/*-------------------------------------------------------------------------
 *
 * typecmds.c
 *	  操作类型（和域）的 SQL 命令的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/typecmds.c
 *
 * DESCRIPTION
 *	  “DefineFoo” 例程接受解析树并挑选出适当的参数/标志，将结果传递给执行实际目录修改的相应 “FooDefine” 例程（在 src/catalog 中）。这些例程还验证用户执行命令的权限。
 *
 * NOTES
 *	  这些事物必须以以下顺序定义和提交：
 *		“create function”：
 *				input/output，recv/send 函数
 *		“create type”：
 *				type
 *		“create operator”：
 *				operators
 *
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "commands/typecmds.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"


/* 获取具有域的关系的结果结构 */
typedef struct
{
	Relation	rel;			/* 打开的和锁定的关系 */
	int			natts;			/* 感兴趣的属性数量 */
	int		   *atts;			/* 属性编号 */
	/* atts[] 的分配长度为 RelationGetNumberOfAttributes(rel) */
} RelToCheck;

/* AlterTypeRecurse() 的参数结构 */
typedef struct
{
	/* 指示要更新的类型属性的标志 */
	bool		updateStorage;
	bool		updateReceive;
	bool		updateSend;
	bool		updateTypmodin;
	bool		updateTypmodout;
	bool		updateAnalyze;
	bool		updateSubscript;
	/* 相关属性的新值 */
	char		storage;
	Oid			receiveOid;
	Oid			sendOid;
	Oid			typmodinOid;
	Oid			typmodoutOid;
	Oid			analyzeOid;
	Oid			subscriptOid;
} AlterTypeRecurseParams;

/* 可能由 pg_upgrade_support 函数设置 */
Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
Oid			binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;

static void fc_makeRangeConstructors(const char *fc_name, Oid fc_namespace,
								  Oid fc_rangeOid, Oid fc_subtype);
static void fc_makeMultirangeConstructors(const char *fc_name, Oid fc_namespace,
									   Oid fc_multirangeOid, Oid fc_rangeOid,
									   Oid fc_rangeArrayOid, Oid *fc_castFuncOid);
static Oid	fc_findTypeInputFunction(List *fc_procname, Oid fc_typeOid);
static Oid	fc_findTypeOutputFunction(List *fc_procname, Oid fc_typeOid);
static Oid	fc_findTypeReceiveFunction(List *fc_procname, Oid fc_typeOid);
static Oid	fc_findTypeSendFunction(List *fc_procname, Oid fc_typeOid);
static Oid	fc_findTypeTypmodinFunction(List *fc_procname);
static Oid	fc_findTypeTypmodoutFunction(List *fc_procname);
static Oid	fc_findTypeAnalyzeFunction(List *fc_procname, Oid fc_typeOid);
static Oid	fc_findTypeSubscriptingFunction(List *fc_procname, Oid fc_typeOid);
static Oid	fc_findRangeSubOpclass(List *fc_opcname, Oid fc_subtype);
static Oid	fc_findRangeCanonicalFunction(List *fc_procname, Oid fc_typeOid);
static Oid	fc_findRangeSubtypeDiffFunction(List *fc_procname, Oid fc_subtype);
static void fc_validateDomainConstraint(Oid fc_domainoid, char *fc_ccbin);
static List *fc_get_rels_with_domain(Oid fc_domainOid, LOCKMODE fc_lockmode);
static void fc_checkEnumOwner(HeapTuple fc_tup);
static char *fc_domainAddConstraint(Oid fc_domainOid, Oid fc_domainNamespace,
								 Oid fc_baseTypeOid,
								 int fc_typMod, Constraint *fc_constr,
								 const char *fc_domainName, ObjectAddress *fc_constrAddr);
static Node *fc_replace_domain_constraint_value(ParseState *fc_pstate,
											 ColumnRef *fc_cref);
static void fc_AlterTypeRecurse(Oid fc_typeOid, bool fc_isImplicitArray,
							 HeapTuple fc_tup, Relation fc_catalog,
							 AlterTypeRecurseParams *fc_atparams);


/*
 * DefineType
 *		注册一个新的基本类型。
 */
ObjectAddress DefineType(ParseState *fc_pstate, List *fc_names, List *fc_parameters)
{
	char	   *fc_typeName;
	Oid			fc_typeNamespace;
	int16		fc_internalLength = -1;	/* 默认：可变长度 */
	List	   *fc_inputName = NIL;
	List	   *fc_outputName = NIL;
	List	   *fc_receiveName = NIL;
	List	   *fc_sendName = NIL;
	List	   *fc_typmodinName = NIL;
	List	   *fc_typmodoutName = NIL;
	List	   *fc_analyzeName = NIL;
	List	   *fc_subscriptName = NIL;
	char		fc_category = TYPCATEGORY_USER;
	bool		fc_preferred = false;
	char		fc_delimiter = DEFAULT_TYPDELIM;
	Oid			fc_elemType = InvalidOid;
	char	   *fc_defaultValue = NULL;
	bool		fc_byValue = false;
	char		fc_alignment = TYPALIGN_INT;	/* 默认对齐 */
	char		fc_storage = TYPSTORAGE_PLAIN; /* 默认 TOAST 存储方法 */
	Oid			fc_collation = InvalidOid;
	DefElem    *fc_likeTypeEl = NULL;
	DefElem    *fc_internalLengthEl = NULL;
	DefElem    *fc_inputNameEl = NULL;
	DefElem    *fc_outputNameEl = NULL;
	DefElem    *fc_receiveNameEl = NULL;
	DefElem    *fc_sendNameEl = NULL;
	DefElem    *fc_typmodinNameEl = NULL;
	DefElem    *fc_typmodoutNameEl = NULL;
	DefElem    *fc_analyzeNameEl = NULL;
	DefElem    *fc_subscriptNameEl = NULL;
	DefElem    *fc_categoryEl = NULL;
	DefElem    *fc_preferredEl = NULL;
	DefElem    *fc_delimiterEl = NULL;
	DefElem    *fc_elemTypeEl = NULL;
	DefElem    *fc_defaultValueEl = NULL;
	DefElem    *fc_byValueEl = NULL;
	DefElem    *fc_alignmentEl = NULL;
	DefElem    *fc_storageEl = NULL;
	DefElem    *fc_collatableEl = NULL;
	Oid			fc_inputOid;
	Oid			fc_outputOid;
	Oid			fc_receiveOid = InvalidOid;
	Oid			fc_sendOid = InvalidOid;
	Oid			fc_typmodinOid = InvalidOid;
	Oid			fc_typmodoutOid = InvalidOid;
	Oid			fc_analyzeOid = InvalidOid;
	Oid			fc_subscriptOid = InvalidOid;
	char	   *fc_array_type;
	Oid			fc_array_oid;
	Oid			fc_typoid;
	ListCell   *fc_pl;
	ObjectAddress fc_address;

	/*
	 * 从 Postgres 8.4 起，我们需要超级用户权限来创建基本
	 * 类型。这纯粹是出于偏执：定义一个不正确的类型（例如，表示
	 * 参数与 C 代码期望的不匹配）有太多方法会搞砸系统。实际上，
	 * 创建 I/O 函数需要超级用户权限，因此，拥有 I/O 函数的先前要求
	 * 无论如何几乎强制要求超级用户权限。我们在这里只是双重确认。
	 *
	 * XXX 如果您移除了此测试，请重新启用下面的 NOT_USED 代码部分。
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to create a base type")));

	/* 将名称列表转换为一个名称和命名空间 */
	fc_typeNamespace = QualifiedNameGetCreationNamespace(fc_names, &fc_typeName);

#ifdef NOT_USED
	/* XXX 鉴于上面的超级用户检查，这个是多余的 */
	/* 检查我们在目标命名空间中是否有创建权限 */
#ifdef FDD
	//解决fc_aclresult未定义的错误
	AclResult fc_aclresult = pg_namespace_aclcheck(fc_typeNamespace, GetUserId(), ACL_CREATE);
#else
	fc_aclresult = pg_namespace_aclcheck(fc_typeNamespace, GetUserId(), ACL_CREATE);
#endif
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_typeNamespace));
#endif

	/*
	 * 查看类型是否已经存在。
	 */
	fc_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
							 CStringGetDatum(fc_typeName),
							 ObjectIdGetDatum(fc_typeNamespace));

	/*
	 * 如果它不是一个空壳，检查它是否是自动生成的数组类型，如果是
	 * 则将其重命名为其他名称。
	 */
	if (OidIsValid(fc_typoid) && get_typisdefined(fc_typoid))
	{
		if (moveArrayTypeName(fc_typoid, fc_typeName, fc_typeNamespace))
			fc_typoid = InvalidOid;
		else
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists", fc_typeName)));
	}

	/*
	 * 如果此命令是无参数的 CREATE TYPE，则我们仅在这里
	 * 创建一个空壳类型，因此这样做（如果已经有一个空壳则失败）。
	 */
	if (fc_parameters == NIL)
	{
		if (OidIsValid(fc_typoid))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists", fc_typeName)));

		fc_address = TypeShellMake(fc_typeName, fc_typeNamespace, GetUserId());
		return fc_address;
	}

	/*
	 * 否则，我们必须已经有一个空壳类型，因为没有其他
	 * 方式可以创建 I/O 函数。
	 */
	if (!OidIsValid(fc_typoid))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("type \"%s\" does not exist", fc_typeName),
				 errhint("Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE.")));

	/* 从参数列表中提取参数 */
	foreach(fc_pl, fc_parameters)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_pl);
		DefElem   **fc_defelp;

		if (strcmp(fc_defel->defname, "like") == 0)
			fc_defelp = &fc_likeTypeEl;
		else if (strcmp(fc_defel->defname, "internallength") == 0)
			fc_defelp = &fc_internalLengthEl;
		else if (strcmp(fc_defel->defname, "input") == 0)
			fc_defelp = &fc_inputNameEl;
		else if (strcmp(fc_defel->defname, "output") == 0)
			fc_defelp = &fc_outputNameEl;
		else if (strcmp(fc_defel->defname, "receive") == 0)
			fc_defelp = &fc_receiveNameEl;
		else if (strcmp(fc_defel->defname, "send") == 0)
			fc_defelp = &fc_sendNameEl;
		else if (strcmp(fc_defel->defname, "typmod_in") == 0)
			fc_defelp = &fc_typmodinNameEl;
		else if (strcmp(fc_defel->defname, "typmod_out") == 0)
			fc_defelp = &fc_typmodoutNameEl;
		else if (strcmp(fc_defel->defname, "analyze") == 0 ||
				 strcmp(fc_defel->defname, "analyse") == 0)
			fc_defelp = &fc_analyzeNameEl;
		else if (strcmp(fc_defel->defname, "subscript") == 0)
			fc_defelp = &fc_subscriptNameEl;
		else if (strcmp(fc_defel->defname, "category") == 0)
			fc_defelp = &fc_categoryEl;
		else if (strcmp(fc_defel->defname, "preferred") == 0)
			fc_defelp = &fc_preferredEl;
		else if (strcmp(fc_defel->defname, "delimiter") == 0)
			fc_defelp = &fc_delimiterEl;
		else if (strcmp(fc_defel->defname, "element") == 0)
			fc_defelp = &fc_elemTypeEl;
		else if (strcmp(fc_defel->defname, "default") == 0)
			fc_defelp = &fc_defaultValueEl;
		else if (strcmp(fc_defel->defname, "passedbyvalue") == 0)
			fc_defelp = &fc_byValueEl;
		else if (strcmp(fc_defel->defname, "alignment") == 0)
			fc_defelp = &fc_alignmentEl;
		else if (strcmp(fc_defel->defname, "storage") == 0)
			fc_defelp = &fc_storageEl;
		else if (strcmp(fc_defel->defname, "collatable") == 0)
			fc_defelp = &fc_collatableEl;
		else
		{
			/* 警告，而不是错误，为历史向后兼容性考虑 */
			ereport(WARNING,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("type attribute \"%s\" not recognized",
							fc_defel->defname),
					 parser_errposition(fc_pstate, fc_defel->location)));
			continue;
		}
		if (*fc_defelp != NULL)
			errorConflictingDefElem(fc_defel, fc_pstate);
		*fc_defelp = fc_defel;
	}

	/*
	 * 现在解释选项；我们单独执行此操作，以便 LIKE 可以
	 * 被其他选项覆盖，而不论参数列表中的顺序如何。
	 */
	if (fc_likeTypeEl)
	{
		Type		fc_likeType;
		Form_pg_type fc_likeForm;

		fc_likeType = typenameType(NULL, defGetTypeName(fc_likeTypeEl), NULL);
		fc_likeForm = (Form_pg_type) GETSTRUCT(fc_likeType);
		fc_internalLength = fc_likeForm->typlen;
		fc_byValue = fc_likeForm->typbyval;
		fc_alignment = fc_likeForm->typalign;
		fc_storage = fc_likeForm->typstorage;
		ReleaseSysCache(fc_likeType);
	}
	if (fc_internalLengthEl)
		fc_internalLength = defGetTypeLength(fc_internalLengthEl);
	if (fc_inputNameEl)
		fc_inputName = defGetQualifiedName(fc_inputNameEl);
	if (fc_outputNameEl)
		fc_outputName = defGetQualifiedName(fc_outputNameEl);
	if (fc_receiveNameEl)
		fc_receiveName = defGetQualifiedName(fc_receiveNameEl);
	if (fc_sendNameEl)
		fc_sendName = defGetQualifiedName(fc_sendNameEl);
	if (fc_typmodinNameEl)
		fc_typmodinName = defGetQualifiedName(fc_typmodinNameEl);
	if (fc_typmodoutNameEl)
		fc_typmodoutName = defGetQualifiedName(fc_typmodoutNameEl);
	if (fc_analyzeNameEl)
		fc_analyzeName = defGetQualifiedName(fc_analyzeNameEl);
	if (fc_subscriptNameEl)
		fc_subscriptName = defGetQualifiedName(fc_subscriptNameEl);
	if (fc_categoryEl)
	{
		char	   *fc_p = defGetString(fc_categoryEl);

		fc_category = fc_p[0];
		/* 限制为非控制 ASCII */
		if (fc_category < 32 || fc_category > 126)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("invalid type category \"%s\": must be simple ASCII",
							fc_p)));
	}
	if (fc_preferredEl)
		fc_preferred = defGetBoolean(fc_preferredEl);
	if (fc_delimiterEl)
	{
		char	   *fc_p = defGetString(fc_delimiterEl);

		fc_delimiter = fc_p[0];
		/* XXX 我们不应该限制分隔符吗？ */
	}
	if (fc_elemTypeEl)
	{
		fc_elemType = typenameTypeId(NULL, defGetTypeName(fc_elemTypeEl));
		/* 不允许伪类型的数组 */
		if (get_typtype(fc_elemType) == TYPTYPE_PSEUDO)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("array element type cannot be %s",
							format_type_be(fc_elemType))));
	}
	if (fc_defaultValueEl)
		fc_defaultValue = defGetString(fc_defaultValueEl);
	if (fc_byValueEl)
		fc_byValue = defGetBoolean(fc_byValueEl);
	if (fc_alignmentEl)
	{
		char	   *fc_a = defGetString(fc_alignmentEl);

		/*
		 * 注意：如果参数是未引用的标识符，解析器将对其
		 * 进行翻译，因此要准备识别翻译后的类型名以及其名义形式。
		 */
		if (pg_strcasecmp(fc_a, "double") == 0 ||
			pg_strcasecmp(fc_a, "float8") == 0 ||
			pg_strcasecmp(fc_a, "pg_catalog.float8") == 0)
			fc_alignment = TYPALIGN_DOUBLE;
		else if (pg_strcasecmp(fc_a, "int4") == 0 ||
				 pg_strcasecmp(fc_a, "pg_catalog.int4") == 0)
			fc_alignment = TYPALIGN_INT;
		else if (pg_strcasecmp(fc_a, "int2") == 0 ||
				 pg_strcasecmp(fc_a, "pg_catalog.int2") == 0)
			fc_alignment = TYPALIGN_SHORT;
		else if (pg_strcasecmp(fc_a, "char") == 0 ||
				 pg_strcasecmp(fc_a, "pg_catalog.bpchar") == 0)
			fc_alignment = TYPALIGN_CHAR;
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("alignment \"%s\" not recognized", fc_a)));
	}
	if (fc_storageEl)
	{
		char	   *fc_a = defGetString(fc_storageEl);

		if (pg_strcasecmp(fc_a, "plain") == 0)
			fc_storage = TYPSTORAGE_PLAIN;
		else if (pg_strcasecmp(fc_a, "external") == 0)
			fc_storage = TYPSTORAGE_EXTERNAL;
		else if (pg_strcasecmp(fc_a, "extended") == 0)
			fc_storage = TYPSTORAGE_EXTENDED;
		else if (pg_strcasecmp(fc_a, "main") == 0)
			fc_storage = TYPSTORAGE_MAIN;
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("storage \"%s\" not recognized", fc_a)));
	}
	if (fc_collatableEl)
		fc_collation = defGetBoolean(fc_collatableEl) ? DEFAULT_COLLATION_OID : InvalidOid;

	/*
	 * 确保我们有所需的定义
	 */
	if (fc_inputName == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type input function must be specified")));
	if (fc_outputName == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type output function must be specified")));

	if (fc_typmodinName == NIL && fc_typmodoutName != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type modifier output function is useless without a type modifier input function")));

	/*
	 * 将 I/O 过程名称转换为 OID
	 */
	fc_inputOid = fc_findTypeInputFunction(fc_inputName, fc_typoid);
	fc_outputOid = fc_findTypeOutputFunction(fc_outputName, fc_typoid);
	if (fc_receiveName)
		fc_receiveOid = fc_findTypeReceiveFunction(fc_receiveName, fc_typoid);
	if (fc_sendName)
		fc_sendOid = fc_findTypeSendFunction(fc_sendName, fc_typoid);

	/*
	 * 将 typmodin/out 函数过程名称转换为 OID。
	 */
	if (fc_typmodinName)
		fc_typmodinOid = fc_findTypeTypmodinFunction(fc_typmodinName);
	if (fc_typmodoutName)
		fc_typmodoutOid = fc_findTypeTypmodoutFunction(fc_typmodoutName);

	/*
	 * 将分析函数过程名称转换为 OID。如果未指定分析函数，
	 * 我们将使用零选择内置默认算法。
	 */
	if (fc_analyzeName)
		fc_analyzeOid = fc_findTypeAnalyzeFunction(fc_analyzeName, fc_typoid);

	/*
	 * 同样，如果有，则查找下标函数。如果未指定，但指定了 typelem，
	 * 如果可以使用 raw_array_subscript_handler，则允许这样做。
	 * （这是出于向后兼容性考虑；也许有一天我们应该抛出错误。）
	 */
	if (fc_subscriptName)
		fc_subscriptOid = fc_findTypeSubscriptingFunction(fc_subscriptName, fc_typoid);
	else if (OidIsValid(fc_elemType))
	{
		if (fc_internalLength > 0 && !fc_byValue && get_typlen(fc_elemType) > 0)
			fc_subscriptOid = F_RAW_ARRAY_SUBSCRIPT_HANDLER;
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("element type cannot be specified without a subscripting function")));
	}

	/*
	 * 检查功能的权限。我们选择要求类型的创建者/拥有者也拥有底层功能。由于创建类型相当于授予对功能的公共执行访问权限，因此最基本的合理检查是执行权限与授予选项。但如果撤销了授予选项，我们没有办法让类型消失，所以拥有权似乎更好。
	 *
	 * XXX 目前考虑到上述超级用户检查，这一切都是不必要的。如果我们放宽这一点，这些调用可能应该移到 findTypeInputFunction 等，供 AlterType 共享。
	 */
#ifdef NOT_USED
	if (fc_inputOid && !pg_proc_ownercheck(fc_inputOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_inputName));
	if (fc_outputOid && !pg_proc_ownercheck(fc_outputOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_outputName));
	if (fc_receiveOid && !pg_proc_ownercheck(fc_receiveOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_receiveName));
	if (fc_sendOid && !pg_proc_ownercheck(fc_sendOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_sendName));
	if (fc_typmodinOid && !pg_proc_ownercheck(fc_typmodinOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_typmodinName));
	if (fc_typmodoutOid && !pg_proc_ownercheck(fc_typmodoutOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_typmodoutName));
	if (fc_analyzeOid && !pg_proc_ownercheck(fc_analyzeOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_analyzeName));
	if (fc_subscriptOid && !pg_proc_ownercheck(fc_subscriptOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
					   NameListToString(fc_subscriptName));
#endif

	/*
	 * 好的，我们完成检查，现在是创建类型的时候了。在调用 TypeCreate 之前，我们必须先分配数组类型的 OID，因为基本类型和数组类型相互引用。
	 */
	fc_array_oid = AssignTypeArrayOid();

	/*
	 * 现在让 TypeCreate 执行所有实际工作。
	 *
	 * 注意：pg_type.oid 在用户表中以数组元素（基本类型）的形式存储在 ArrayType 中，复合类型中存储在 DatumTupleFields 中。这个 oid 必须在二进制升级中被保留。
	 */
	fc_address =
		TypeCreate(InvalidOid,	/* 没有预先确定的类型 OID */
				   fc_typeName,	/* 类型名称 */
				   fc_typeNamespace,	/* 名称空间 */
				   InvalidOid,	/* 关系 oid（在这里不适用） */
				   0,			/* 关系类型（同上） */
				   GetUserId(), /* 所有者的 ID */
				   fc_internalLength,	/* 内部大小 */
				   TYPTYPE_BASE,	/* 类型-类型（基本类型） */
				   fc_category,	/* 类型类别 */
				   fc_preferred,	/* 这是一种优选类型吗？ */
				   fc_delimiter,	/* 数组元素分隔符 */
				   fc_inputOid,	/* 输入过程 */
				   fc_outputOid,	/* 输出过程 */
				   fc_receiveOid,	/* 接收过程 */
				   fc_sendOid,		/* 发送过程 */
				   fc_typmodinOid, /* typmodin 过程 */
				   fc_typmodoutOid,	/* typmodout 过程 */
				   fc_analyzeOid,	/* 分析过程 */
				   fc_subscriptOid,	/* 下标过程 */
				   fc_elemType,	/* 元素类型 ID */
				   false,		/* 这不是隐式数组类型 */
				   fc_array_oid,	/* 我们即将创建的数组类型 */
				   InvalidOid,	/* 基本类型 ID（仅适用于域） */
				   fc_defaultValue,	/* 默认类型值 */
				   NULL,		/* 没有可用的二进制形式 */
				   fc_byValue,		/* 按值传递 */
				   fc_alignment,	/* 所需对齐 */
				   fc_storage,		/* TOAST 策略 */
				   -1,			/* typMod（仅适用于域） */
				   0,			/* typbasetype 的数组维度 */
				   false,		/* 类型不为 NULL */
				   fc_collation);	/* 类型的排序规则 */
	Assert(fc_typoid == fc_address.objectId);

	/*
	 * 创建与之对应的数组类型。
	 */
	fc_array_type = makeArrayTypeName(fc_typeName, fc_typeNamespace);

	/* 对于数组， 对齐必须是 TYPALIGN_INT 或 TYPALIGN_DOUBLE */
	fc_alignment = (fc_alignment == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;

	TypeCreate(fc_array_oid,		/* 强制分配这个类型的 OID */
			   fc_array_type,		/* 类型名称 */
			   fc_typeNamespace,	/* 名称空间 */
			   InvalidOid,		/* 关系 oid（在这里不适用） */
			   0,				/* 关系类型（同上） */
			   GetUserId(),		/* 所有者的 ID */
			   -1,				/* 内部大小（始终为 varlena） */
			   TYPTYPE_BASE,	/* 类型-类型（基本类型） */
			   TYPCATEGORY_ARRAY,	/* 类型类别（数组） */
			   false,			/* 数组类型从不优选 */
			   fc_delimiter,		/* 数组元素分隔符 */
			   F_ARRAY_IN,		/* 输入过程 */
			   F_ARRAY_OUT,		/* 输出过程 */
			   F_ARRAY_RECV,	/* 接收过程 */
			   F_ARRAY_SEND,	/* 发送过程 */
			   fc_typmodinOid,		/* typmodin 过程 */
			   fc_typmodoutOid,	/* typmodout 过程 */
			   F_ARRAY_TYPANALYZE,	/* 分析过程 */
			   F_ARRAY_SUBSCRIPT_HANDLER,	/* 数组下标过程 */
			   fc_typoid,			/* 元素类型 ID */
			   true,			/* 是的，这是一种数组类型 */
			   InvalidOid,		/* 无进一步的数组类型 */
			   InvalidOid,		/* 基本类型 ID */
			   NULL,			/* 从不为默认类型值 */
			   NULL,			/* 二进制默认值也不被发送 */
			   false,			/* 从不按值传递 */
			   fc_alignment,		/* 详见上文 */
			   TYPSTORAGE_EXTENDED, /* ARRAY 始终可压缩 */
			   -1,				/* typMod（仅适用于域） */
			   0,				/* typbasetype 的数组维度 */
			   false,			/* 类型不为 NULL */
			   fc_collation);		/* 类型的排序规则 */

	pfree(fc_array_type);

	return fc_address;
}

/*
 * 类型删除的核心部分。
 */
void RemoveTypeById(Oid fc_typeOid)
{
	Relation	fc_relation;
	HeapTuple	fc_tup;

	fc_relation = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typeOid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_typeOid);

	CatalogTupleDelete(fc_relation, &fc_tup->t_self);

	/*
	 * 如果是枚举类型，删除 pg_enum 条目；我们不费心为这些
	 * 创建依赖条目，所以必须在这里“手动”处理。
	 */
	if (((Form_pg_type) GETSTRUCT(fc_tup))->typtype == TYPTYPE_ENUM)
		EnumValuesDelete(fc_typeOid);

	/*
	 * 如果是范围类型，删除 pg_range 条目；我们不费心
	 * 为此创建依赖条目，所以必须在这里“手动”处理。
	 */
	if (((Form_pg_type) GETSTRUCT(fc_tup))->typtype == TYPTYPE_RANGE)
		RangeDelete(fc_typeOid);

	ReleaseSysCache(fc_tup);

	table_close(fc_relation, RowExclusiveLock);
}


/*
 * DefineDomain
 *		注册一个新域。
 */
ObjectAddress DefineDomain(CreateDomainStmt *fc_stmt)
{
	char	   *fc_domainName;
	char	   *fc_domainArrayName;
	Oid			fc_domainNamespace;
	AclResult	fc_aclresult;
	int16		fc_internalLength;
	Oid			fc_inputProcedure;
	Oid			fc_outputProcedure;
	Oid			fc_receiveProcedure;
	Oid			fc_sendProcedure;
	Oid			fc_analyzeProcedure;
	bool		fc_byValue;
	char		fc_category;
	char		fc_delimiter;
	char		fc_alignment;
	char		fc_storage;
	char		fc_typtype;
	Datum		fc_datum;
	bool		fc_isnull;
	char	   *fc_defaultValue = NULL;
	char	   *fc_defaultValueBin = NULL;
	bool		fc_saw_default = false;
	bool		fc_typNotNull = false;
	bool		fc_nullDefined = false;
	int32		fc_typNDims = list_length(fc_stmt->typeName->arrayBounds);
	HeapTuple	fc_typeTup;
	List	   *fc_schema = fc_stmt->constraints;
	ListCell   *fc_listptr;
	Oid			fc_basetypeoid;
	Oid			fc_old_type_oid;
	Oid			fc_domaincoll;
	Oid			fc_domainArrayOid;
	Form_pg_type fc_baseType;
	int32		fc_basetypeMod;
	Oid			fc_baseColl;
	ObjectAddress fc_address;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_domainNamespace = QualifiedNameGetCreationNamespace(fc_stmt->domainname,
														&fc_domainName);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_domainNamespace, GetUserId(),
									  ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_domainNamespace));

	/*
	 * 检查是否与现有类型名称发生冲突。如果有，并且它是一个
	 * 自动生成的数组，我们可以将其重命名。
	 */
	fc_old_type_oid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
								   CStringGetDatum(fc_domainName),
								   ObjectIdGetDatum(fc_domainNamespace));
	if (OidIsValid(fc_old_type_oid))
	{
		if (!moveArrayTypeName(fc_old_type_oid, fc_domainName, fc_domainNamespace))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists", fc_domainName)));
	}

	/*
	 * 查找基本类型。
	 */
	fc_typeTup = typenameType(NULL, fc_stmt->typeName, &fc_basetypeMod);
	fc_baseType = (Form_pg_type) GETSTRUCT(fc_typeTup);
	fc_basetypeoid = fc_baseType->oid;

	/*
	 * 基本类型必须是普通基本类型、复合类型、另一个域、
	 * 枚举或范围类型。对伪类型的域将产生安全漏洞。
	 * （这段代码如果只检查伪类型会更简单；但是
	 * 似乎更安全明确列出支持的特定类型，而不是假设未来的
	 * 所有类型都将自动支持。）
	 */
	fc_typtype = fc_baseType->typtype;
	if (fc_typtype != TYPTYPE_BASE &&
		fc_typtype != TYPTYPE_COMPOSITE &&
		fc_typtype != TYPTYPE_DOMAIN &&
		fc_typtype != TYPTYPE_ENUM &&
		fc_typtype != TYPTYPE_RANGE &&
		fc_typtype != TYPTYPE_MULTIRANGE)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("\"%s\" is not a valid base type for a domain",
						TypeNameToString(fc_stmt->typeName))));

	fc_aclresult = pg_type_aclcheck(fc_basetypeoid, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error_type(fc_aclresult, fc_basetypeoid);

	/*
	 * 收集新域的属性。有些是从基本类型继承的，有些不是。
	 * 如果您更改任何这种继承行为，请确保更新
	 * AlterTypeRecurse() 以匹配！
	 */

	/*
	 * 如果有，识别排序规则
	 */
	fc_baseColl = fc_baseType->typcollation;
	if (fc_stmt->collClause)
		fc_domaincoll = get_collation_oid(fc_stmt->collClause->collname, false);
	else
		fc_domaincoll = fc_baseColl;

	/* 如果 COLLATE 应用于不可排序类型，则进行抱怨 */
	if (OidIsValid(fc_domaincoll) && !OidIsValid(fc_baseColl))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("collations are not supported by type %s",
						format_type_be(fc_basetypeoid))));

	/* 按值传递 */
	fc_byValue = fc_baseType->typbyval;

	/* 所需对齐方式 */
	fc_alignment = fc_baseType->typalign;

	/* TOAST 策略 */
	fc_storage = fc_baseType->typstorage;

	/* 存储长度 */
	fc_internalLength = fc_baseType->typlen;

	/* 类型类别 */
	fc_category = fc_baseType->typcategory;

	/* 数组元素分隔符 */
	fc_delimiter = fc_baseType->typdelim;

	/* I/O 函数 */
	fc_inputProcedure = F_DOMAIN_IN;
	fc_outputProcedure = fc_baseType->typoutput;
	fc_receiveProcedure = F_DOMAIN_RECV;
	fc_sendProcedure = fc_baseType->typsend;

	/* 域永远不接受 typmods，因此不需要 typmodin/typmodout */

	/* 分析函数 */
	fc_analyzeProcedure = fc_baseType->typanalyze;

	/*
	 * 域不需要下标函数，因为它们本身不可下标。
	 * 如果基本类型可下标，解析器将在下标之前将类型
	 * 简化为基本类型。
	 */

	/* 继承的默认值 */
	fc_datum = SysCacheGetAttr(TYPEOID, fc_typeTup,
							Anum_pg_type_typdefault, &fc_isnull);
	if (!fc_isnull)
		fc_defaultValue = TextDatumGetCString(fc_datum);

	/* 继承的默认二进制值 */
	fc_datum = SysCacheGetAttr(TYPEOID, fc_typeTup,
							Anum_pg_type_typdefaultbin, &fc_isnull);
	if (!fc_isnull)
		fc_defaultValueBin = TextDatumGetCString(fc_datum);

	/*
	 * 手动遍历约束，以避免 DefineRelation() 和相关函数
	 * 进行的额外处理。
	 */
	foreach(fc_listptr, fc_schema)
	{
		Constraint *fc_constr = lfirst(fc_listptr);

		if (!IsA(fc_constr, Constraint))
			elog(ERROR, "unrecognized node type: %d",
				 (int) nodeTag(fc_constr));
		switch (fc_constr->contype)
		{
			case CONSTR_DEFAULT:

				/*
				 * 继承的默认值可以被用户用 DEFAULT <expr> 子句覆盖
				 * ... 但只能覆盖一次。
				 */
				if (fc_saw_default)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple default expressions")));
				fc_saw_default = true;

				if (fc_constr->raw_expr)
				{
					ParseState *fc_pstate;
					Node	   *fc_defaultExpr;

					/* 为 transformExpr 创建一个虚拟 ParseState */
					fc_pstate = make_parsestate(NULL);

					/*
					 * 将 constr->raw_expr 处理成一个表达式。注意：
					 * 名称仅用于错误信息
					 */
					fc_defaultExpr = cookDefault(fc_pstate, fc_constr->raw_expr,
											  fc_basetypeoid,
											  fc_basetypeMod,
											  fc_domainName,
											  0);

					/*
					 * 如果表达式只是一个 NULL 常量，我们将其视为没有默认值。
					 *
					 * 注意，如果基本类型是另一个域，我们会在这里看到一个 CoerceToDomain 表达式，而不是丢弃默认值。
					 * 这很关键，因为域的默认值需要保留以覆盖基础域可能具有的任何默认值。
					 */
					if (fc_defaultExpr == NULL ||
						(IsA(fc_defaultExpr, Const) &&
						 ((Const *) fc_defaultExpr)->constisnull))
					{
						fc_defaultValue = NULL;
						fc_defaultValueBin = NULL;
					}
					else
					{
						/*
						 * 表达式必须作为 nodeToString 结果存储，
						 * 但我们还需要一个有效的文本表示
						 * （主要是为了让 pg_dump 的工作更轻松）。
						 */
						fc_defaultValue =
							deparse_expression(fc_defaultExpr,
											   NIL, false, false);
						fc_defaultValueBin = nodeToString(fc_defaultExpr);
					}
				}
				else
				{
					/* 没有默认值（这还会发生吗？） */
					fc_defaultValue = NULL;
					fc_defaultValueBin = NULL;
				}
				break;

			case CONSTR_NOTNULL:
				if (fc_nullDefined && !fc_typNotNull)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("conflicting NULL/NOT NULL constraints")));
				fc_typNotNull = true;
				fc_nullDefined = true;
				break;

			case CONSTR_NULL:
				if (fc_nullDefined && fc_typNotNull)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("conflicting NULL/NOT NULL constraints")));
				fc_typNotNull = false;
				fc_nullDefined = true;
				break;

			case CONSTR_CHECK:

				/*
				 * 检查约束在域创建后处理，因为它们需要域的 Oid；
				 * 在这一点上，我们只能检查它们是否没有标记为 NO INHERIT，
				 * 因为这将是虚假的。
				 */
				if (fc_constr->is_no_inherit)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("check constraints for domains cannot be marked NO INHERIT")));
				break;

				/*
				 * 其他都是错误情况
				 */
			case CONSTR_UNIQUE:
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("unique constraints not possible for domains")));
				break;

			case CONSTR_PRIMARY:
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("primary key constraints not possible for domains")));
				break;

			case CONSTR_EXCLUSION:
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("exclusion constraints not possible for domains")));
				break;

			case CONSTR_FOREIGN:
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("foreign key constraints not possible for domains")));
				break;

			case CONSTR_ATTR_DEFERRABLE:
			case CONSTR_ATTR_NOT_DEFERRABLE:
			case CONSTR_ATTR_DEFERRED:
			case CONSTR_ATTR_IMMEDIATE:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("specifying constraint deferrability not supported for domains")));
				break;

			default:
				elog(ERROR, "unrecognized constraint subtype: %d",
					 (int) fc_constr->contype);
				break;
		}
	}

	/* 为数组类型分配 OID */
	fc_domainArrayOid = AssignTypeArrayOid();

	/*
	 * 让 TypeCreate 做所有实际工作。
	 */
	fc_address =
		TypeCreate(InvalidOid,	/* 没有预先确定的类型 OID */
				   fc_domainName,	/* 类型名称 */
				   fc_domainNamespace, /* 名称空间 */
				   InvalidOid,	/* 关系 oid（在这里不适用） */
				   0,			/* 关系类型（同上） */
				   GetUserId(), /* 所有者的 ID */
				   fc_internalLength,	/* 内部大小 */
				   TYPTYPE_DOMAIN,	/* 类型类型（域类型） */
				   fc_category,	/* 类型类别 */
				   false,		/* 域类型从不优先 */
				   fc_delimiter,	/* 数组元素分隔符 */
				   fc_inputProcedure,	/* 输入过程 */
				   fc_outputProcedure, /* 输出过程 */
				   fc_receiveProcedure,	/* 接收过程 */
				   fc_sendProcedure,	/* 发送过程 */
				   InvalidOid,	/* typmodin 过程 - 无 */
				   InvalidOid,	/* typmodout 过程 - 无 */
				   fc_analyzeProcedure,	/* 分析过程 */
				   InvalidOid,	/* 下标过程 - 无 */
				   InvalidOid,	/* 没有数组元素类型 */
				   false,		/* 这不是一个数组 */
				   fc_domainArrayOid,	/* 我们即将创建的数组类型 */
				   fc_basetypeoid, /* 基本类型 ID */
				   fc_defaultValue,	/* 默认类型值（文本） */
				   fc_defaultValueBin, /* 默认类型值（二进制） */
				   fc_byValue,		/* 按值传递 */
				   fc_alignment,	/* 所需对齐 */
				   fc_storage,		/* TOAST 策略 */
				   fc_basetypeMod, /* typeMod 值 */
				   fc_typNDims,	/* 基本类型的数组维度 */
				   fc_typNotNull,	/* 类型不为 NULL */
				   fc_domaincoll); /* 类型的排序规则 */

	/*
	 * 创建与之对应的数组类型。
	 */
	fc_domainArrayName = makeArrayTypeName(fc_domainName, fc_domainNamespace);

	/* 对于数组， 对齐必须是 TYPALIGN_INT 或 TYPALIGN_DOUBLE */
	fc_alignment = (fc_alignment == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;

	TypeCreate(fc_domainArrayOid,	/* 强制分配这个类型的 OID */
			   fc_domainArrayName, /* 类型名称 */
			   fc_domainNamespace, /* 名称空间 */
			   InvalidOid,		/* 关系 oid（在这里不适用） */
			   0,				/* 关系类型（同上） */
			   GetUserId(),		/* 所有者的 ID */
			   -1,				/* 内部大小（始终为 varlena） */
			   TYPTYPE_BASE,	/* 类型-类型（基本类型） */
			   TYPCATEGORY_ARRAY,	/* 类型类别（数组） */
			   false,			/* 数组类型从不优选 */
			   fc_delimiter,		/* 数组元素分隔符 */
			   F_ARRAY_IN,		/* 输入过程 */
			   F_ARRAY_OUT,		/* 输出过程 */
			   F_ARRAY_RECV,	/* 接收过程 */
			   F_ARRAY_SEND,	/* 发送过程 */
			   InvalidOid,		/* typmodin 过程 - 无 */
			   InvalidOid,		/* typmodout 过程 - 无 */
			   F_ARRAY_TYPANALYZE,	/* 分析过程 */
			   F_ARRAY_SUBSCRIPT_HANDLER,	/* 数组下标过程 */
			   fc_address.objectId,	/* 元素类型 ID */
			   true,			/* 是的，这是一种数组类型 */
			   InvalidOid,		/* 无进一步的数组类型 */
			   InvalidOid,		/* 基本类型 ID */
			   NULL,			/* 从不为默认类型值 */
			   NULL,			/* 二进制默认值也不被发送 */
			   false,			/* 从不按值传递 */
			   fc_alignment,		/* 详见上文 */
			   TYPSTORAGE_EXTENDED, /* ARRAY 始终可压缩 */
			   -1,				/* typMod（仅适用于域） */
			   0,				/* typbasetype 的数组维度 */
			   false,			/* 类型不为 NULL */
			   fc_domaincoll);		/* 类型的排序规则 */

	pfree(fc_domainArrayName);

	/*
	 * 处理引用由TypeCreate返回的域ID的约束
	 */
	foreach(fc_listptr, fc_schema)
	{
		Constraint *fc_constr = lfirst(fc_listptr);

		/* 它必须是一个约束，根据上述检查 */

		switch (fc_constr->contype)
		{
			case CONSTR_CHECK:
				fc_domainAddConstraint(fc_address.objectId, fc_domainNamespace,
									fc_basetypeoid, fc_basetypeMod,
									fc_constr, fc_domainName, NULL);
				break;

				/* 其他约束类型在上面已完全处理 */

			default:
				break;
		}

		/* CCI以便我们可以检测重复的约束名称 */
		CommandCounterIncrement();
	}

	/*
	 * 现在我们可以清理了。
	 */
	ReleaseSysCache(fc_typeTup);

	return fc_address;
}


/*
 * DefineEnum
 *		注册一个新的枚举。
 */
ObjectAddress DefineEnum(CreateEnumStmt *fc_stmt)
{
	char	   *fc_enumName;
	char	   *fc_enumArrayName;
	Oid			fc_enumNamespace;
	AclResult	fc_aclresult;
	Oid			fc_old_type_oid;
	Oid			fc_enumArrayOid;
	ObjectAddress fc_enumTypeAddr;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_enumNamespace = QualifiedNameGetCreationNamespace(fc_stmt->typeName,
													  &fc_enumName);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_enumNamespace, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_enumNamespace));

	/*
	 * 检查是否与现有类型名称发生冲突。如果有，并且它是一个
	 * 自动生成的数组，我们可以将其重命名。
	 */
	fc_old_type_oid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
								   CStringGetDatum(fc_enumName),
								   ObjectIdGetDatum(fc_enumNamespace));
	if (OidIsValid(fc_old_type_oid))
	{
		if (!moveArrayTypeName(fc_old_type_oid, fc_enumName, fc_enumNamespace))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists", fc_enumName)));
	}

	/* 为数组类型分配 OID */
	fc_enumArrayOid = AssignTypeArrayOid();

	/* 创建pg_type条目 */
	fc_enumTypeAddr =
		TypeCreate(InvalidOid,	/* 没有预先确定的类型 OID */
				   fc_enumName,	/* 类型名称 */
				   fc_enumNamespace,	/* 名称空间 */
				   InvalidOid,	/* 关系 oid（在这里不适用） */
				   0,			/* 关系类型（同上） */
				   GetUserId(), /* 所有者的 ID */
				   sizeof(Oid), /* 内部大小 */
				   TYPTYPE_ENUM,	/* 类型-类型（枚举类型） */
				   TYPCATEGORY_ENUM,	/* 类型-类别（枚举类型） */
				   false,		/* 枚举类型绝不会被优先选择 */
				   DEFAULT_TYPDELIM,	/* 数组元素分隔符 */
				   F_ENUM_IN,	/* 输入过程 */
				   F_ENUM_OUT,	/* 输出过程 */
				   F_ENUM_RECV, /* 接收过程 */
				   F_ENUM_SEND, /* 发送过程 */
				   InvalidOid,	/* typmodin 过程 - 无 */
				   InvalidOid,	/* typmodout 过程 - 无 */
				   InvalidOid,	/* 分析过程 - 默认 */
				   InvalidOid,	/* 下标过程 - 无 */
				   InvalidOid,	/* 元素类型 ID */
				   false,		/* 这不是一个数组类型 */
				   fc_enumArrayOid,	/* 我们即将创建的数组类型 */
				   InvalidOid,	/* 基本类型 ID（仅适用于域） */
				   NULL,		/* 从不为默认类型值 */
				   NULL,		/* 二进制默认值也不被发送 */
				   true,		/* 始终按值传递 */
				   TYPALIGN_INT,	/* 整数对齐 */
				   TYPSTORAGE_PLAIN,	/* TOAST策略始终为简单 */
				   -1,			/* typMod（仅适用于域） */
				   0,			/* typbasetype 的数组维度 */
				   false,		/* 类型不为 NULL */
				   InvalidOid); /* 类型的排序规则 */

	/* 将枚举的值输入到pg_enum */
	EnumValuesCreate(fc_enumTypeAddr.objectId, fc_stmt->vals);

	/*
	 * 创建与之对应的数组类型。
	 */
	fc_enumArrayName = makeArrayTypeName(fc_enumName, fc_enumNamespace);

	TypeCreate(fc_enumArrayOid,	/* 强制分配这个类型的 OID */
			   fc_enumArrayName,	/* 类型名称 */
			   fc_enumNamespace,	/* 名称空间 */
			   InvalidOid,		/* 关系 oid（在这里不适用） */
			   0,				/* 关系类型（同上） */
			   GetUserId(),		/* 所有者的 ID */
			   -1,				/* 内部大小（始终为 varlena） */
			   TYPTYPE_BASE,	/* 类型-类型（基本类型） */
			   TYPCATEGORY_ARRAY,	/* 类型类别（数组） */
			   false,			/* 数组类型从不优选 */
			   DEFAULT_TYPDELIM,	/* 数组元素分隔符 */
			   F_ARRAY_IN,		/* 输入过程 */
			   F_ARRAY_OUT,		/* 输出过程 */
			   F_ARRAY_RECV,	/* 接收过程 */
			   F_ARRAY_SEND,	/* 发送过程 */
			   InvalidOid,		/* typmodin 过程 - 无 */
			   InvalidOid,		/* typmodout 过程 - 无 */
			   F_ARRAY_TYPANALYZE,	/* 分析过程 */
			   F_ARRAY_SUBSCRIPT_HANDLER,	/* 数组下标过程 */
			   fc_enumTypeAddr.objectId,	/* 元素类型 ID */
			   true,			/* 是的，这是一种数组类型 */
			   InvalidOid,		/* 无进一步的数组类型 */
			   InvalidOid,		/* 基本类型 ID */
			   NULL,			/* 从不为默认类型值 */
			   NULL,			/* 二进制默认值也不被发送 */
			   false,			/* 从不按值传递 */
			   TYPALIGN_INT,	/* 枚举具有整数对齐，它们的数组也是如此 */
			   TYPSTORAGE_EXTENDED, /* ARRAY 始终可压缩 */
			   -1,				/* typMod（仅适用于域） */
			   0,				/* typbasetype 的数组维度 */
			   false,			/* 类型不为 NULL */
			   InvalidOid);		/* 类型的排序规则 */

	pfree(fc_enumArrayName);

	return fc_enumTypeAddr;
}

/*
 * AlterEnum
 *		向现有枚举添加新标签。
 */
ObjectAddress AlterEnum(AlterEnumStmt *fc_stmt)
{
	Oid			fc_enum_type_oid;
	TypeName   *fc_typename;
	HeapTuple	fc_tup;
	ObjectAddress fc_address;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_stmt->typeName);
	fc_enum_type_oid = typenameTypeId(NULL, fc_typename);

	fc_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_enum_type_oid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_enum_type_oid);

	/* 检查它是否为枚举，并检查用户是否有权限修改该枚举 */
	fc_checkEnumOwner(fc_tup);

	ReleaseSysCache(fc_tup);

	if (fc_stmt->oldVal)
	{
		/* 重命名现有标签 */
		RenameEnumLabel(fc_enum_type_oid, fc_stmt->oldVal, fc_stmt->newVal);
	}
	else
	{
		/* 添加新标签 */
		AddEnumLabel(fc_enum_type_oid, fc_stmt->newVal,
					 fc_stmt->newValNeighbor, fc_stmt->newValIsAfter,
					 fc_stmt->skipIfNewValExists);
	}

	InvokeObjectPostAlterHook(TypeRelationId, fc_enum_type_oid, 0);

	ObjectAddressSet(fc_address, TypeRelationId, fc_enum_type_oid);

	return fc_address;
}


/*
 * checkEnumOwner
 *
 * 检查该类型是否确实是枚举，并且当前用户
 * 有权限对其进行 ALTER TYPE。如果没有，抛出错误。
 */
static void fc_checkEnumOwner(HeapTuple fc_tup)
{
	Form_pg_type fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 检查这是否确实是一个枚举 */
	if (fc_typTup->typtype != TYPTYPE_ENUM)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is not an enum",
						format_type_be(fc_typTup->oid))));

	/* 权限检查：必须拥有类型 */
	if (!pg_type_ownercheck(fc_typTup->oid, GetUserId()))
		aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typTup->oid);
}


/*
 * DefineRange
 *		注册一个新的范围类型。
 *
 * 也许将 pg_type.typelem 设置为基本类型是值得的，
 * 同样在多范围上将其设置为范围类型也是如此。但是拥有一个
 * 非零的 typelem 在其他地方被视为是数组的同义词，
 * 而用户可能有基于这一假设的查询。
 */
ObjectAddress DefineRange(ParseState *fc_pstate, CreateRangeStmt *fc_stmt)
{
	char	   *fc_typeName;
	Oid			fc_typeNamespace;
	Oid			fc_typoid;
	char	   *fc_rangeArrayName;
	char	   *fc_multirangeTypeName = NULL;
	char	   *fc_multirangeArrayName;
	Oid			fc_multirangeNamespace = InvalidOid;
	Oid			fc_rangeArrayOid;
	Oid			fc_multirangeOid;
	Oid			fc_multirangeArrayOid;
	Oid			fc_rangeSubtype = InvalidOid;
	List	   *fc_rangeSubOpclassName = NIL;
	List	   *fc_rangeCollationName = NIL;
	List	   *fc_rangeCanonicalName = NIL;
	List	   *fc_rangeSubtypeDiffName = NIL;
	Oid			fc_rangeSubOpclass;
	Oid			fc_rangeCollation;
	regproc		fc_rangeCanonical;
	regproc		fc_rangeSubtypeDiff;
	int16		fc_subtyplen;
	bool		fc_subtypbyval;
	char		fc_subtypalign;
	char		fc_alignment;
	AclResult	fc_aclresult;
	ListCell   *fc_lc;
	ObjectAddress fc_address;
	ObjectAddress fc_mltrngaddress PG_USED_FOR_ASSERTS_ONLY;
	Oid			fc_castFuncOid;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_typeNamespace = QualifiedNameGetCreationNamespace(fc_stmt->typeName,
													  &fc_typeName);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_typeNamespace, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_typeNamespace));

	/*
	 * 查看类型是否已经存在。
	 */
	fc_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
							 CStringGetDatum(fc_typeName),
							 ObjectIdGetDatum(fc_typeNamespace));

	/*
	 * 如果它不是一个空壳，检查它是否是自动生成的数组类型，如果是
	 * 则将其重命名为其他名称。
	 */
	if (OidIsValid(fc_typoid) && get_typisdefined(fc_typoid))
	{
		if (moveArrayTypeName(fc_typoid, fc_typeName, fc_typeNamespace))
			fc_typoid = InvalidOid;
		else
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists", fc_typeName)));
	}

	/*
	 * 与 DefineType() 不同，我们不坚持先存在一个壳类型，
	 * 因为它只在用户希望指定规范函数时需要。
	 */

	/* 从参数列表中提取参数 */
	foreach(fc_lc, fc_stmt->params)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_defel->defname, "subtype") == 0)
		{
			if (OidIsValid(fc_rangeSubtype))
				errorConflictingDefElem(fc_defel, fc_pstate);
			/* 我们可以立即查找子类型名称 */
			fc_rangeSubtype = typenameTypeId(NULL, defGetTypeName(fc_defel));
		}
		else if (strcmp(fc_defel->defname, "subtype_opclass") == 0)
		{
			if (fc_rangeSubOpclassName != NIL)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_rangeSubOpclassName = defGetQualifiedName(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "collation") == 0)
		{
			if (fc_rangeCollationName != NIL)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_rangeCollationName = defGetQualifiedName(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "canonical") == 0)
		{
			if (fc_rangeCanonicalName != NIL)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_rangeCanonicalName = defGetQualifiedName(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "subtype_diff") == 0)
		{
			if (fc_rangeSubtypeDiffName != NIL)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_rangeSubtypeDiffName = defGetQualifiedName(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "multirange_type_name") == 0)
		{
			if (fc_multirangeTypeName != NULL)
				errorConflictingDefElem(fc_defel, fc_pstate);
			/* 我们可以立即查找子类型名称 */
			fc_multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(fc_defel),
																	&fc_multirangeTypeName);
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("type attribute \"%s\" not recognized",
							fc_defel->defname)));
	}

	/* 必须有一个子类型 */
	if (!OidIsValid(fc_rangeSubtype))
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("type attribute \"subtype\" is required")));
	/* 不允许伪类型的范围 */
	if (get_typtype(fc_rangeSubtype) == TYPTYPE_PSEUDO)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("range subtype cannot be %s",
						format_type_be(fc_rangeSubtype))));

	/* 确定子操作类 */
	fc_rangeSubOpclass = fc_findRangeSubOpclass(fc_rangeSubOpclassName, fc_rangeSubtype);

	/* 确定要使用的排序规则（如果有） */
	if (type_is_collatable(fc_rangeSubtype))
	{
		if (fc_rangeCollationName != NIL)
			fc_rangeCollation = get_collation_oid(fc_rangeCollationName, false);
		else
			fc_rangeCollation = get_typcollation(fc_rangeSubtype);
	}
	else
	{
		if (fc_rangeCollationName != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("range collation specified but subtype does not support collation")));
		fc_rangeCollation = InvalidOid;
	}

	/* 确定支持函数（如果提供） */
	if (fc_rangeCanonicalName != NIL)
	{
		if (!OidIsValid(fc_typoid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cannot specify a canonical function without a pre-created shell type"),
					 errhint("Create the type as a shell type, then create its canonicalization function, then do a full CREATE TYPE.")));
		fc_rangeCanonical = fc_findRangeCanonicalFunction(fc_rangeCanonicalName,
													fc_typoid);
	}
	else
		fc_rangeCanonical = InvalidOid;

	if (fc_rangeSubtypeDiffName != NIL)
		fc_rangeSubtypeDiff = fc_findRangeSubtypeDiffFunction(fc_rangeSubtypeDiffName,
														fc_rangeSubtype);
	else
		fc_rangeSubtypeDiff = InvalidOid;

	get_typlenbyvalalign(fc_rangeSubtype,
						 &fc_subtyplen, &fc_subtypbyval, &fc_subtypalign);

	/* 对于范围，排列必须是 TYPALIGN_INT 或 TYPALIGN_DOUBLE */
	fc_alignment = (fc_subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;

	/* 为数组类型、其多范围和其多范围数组分配 OID */
	fc_rangeArrayOid = AssignTypeArrayOid();
	fc_multirangeOid = AssignTypeMultirangeOid();
	fc_multirangeArrayOid = AssignTypeMultirangeArrayOid();

	/* 创建pg_type条目 */
	fc_address =
		TypeCreate(InvalidOid,	/* 没有预先确定的类型 OID */
				   fc_typeName,	/* 类型名称 */
				   fc_typeNamespace,	/* 名称空间 */
				   InvalidOid,	/* 关系 oid（在这里不适用） */
				   0,			/* 关系类型（同上） */
				   GetUserId(), /* 所有者的 ID */
				   -1,			/* 内部大小（始终为 varlena） */
				   TYPTYPE_RANGE,	/* 类型类型（范围类型） */
				   TYPCATEGORY_RANGE,	/* 类型类别（范围类型） */
				   false,		/* 范围类型永远不会被优先考虑 */
				   DEFAULT_TYPDELIM,	/* 数组元素分隔符 */
				   F_RANGE_IN,	/* 输入过程 */
				   F_RANGE_OUT, /* 输出过程 */
				   F_RANGE_RECV,	/* 接收过程 */
				   F_RANGE_SEND,	/* 发送过程 */
				   InvalidOid,	/* typmodin 过程 - 无 */
				   InvalidOid,	/* typmodout 过程 - 无 */
				   F_RANGE_TYPANALYZE,	/* 分析过程 */
				   InvalidOid,	/* 下标过程 - 无 */
				   InvalidOid,	/* 元素类型 ID - 无 */
				   false,		/* 这不是一个数组类型 */
				   fc_rangeArrayOid,	/* 我们即将创建的数组类型 */
				   InvalidOid,	/* 基本类型 ID（仅适用于域） */
				   NULL,		/* 从不为默认类型值 */
				   NULL,		/* 也没有可用的二进制形式 */
				   false,		/* 从不按值传递 */
				   fc_alignment,	/* 对齐方式 */
				   TYPSTORAGE_EXTENDED, /* TOAST 策略（始终为扩展） */
				   -1,			/* typMod（仅适用于域） */
				   0,			/* typbasetype 的数组维度 */
				   false,		/* 类型不为 NULL */
				   InvalidOid); /* 类型的排序规则（范围类型从不具有） */
	Assert(fc_typoid == InvalidOid || fc_typoid == fc_address.objectId);
	fc_typoid = fc_address.objectId;

	/* 创建与之对应的多范围 */
	if (fc_multirangeTypeName)
	{
		Oid			fc_old_typoid;

		/*
		 * 查看多范围类型是否已经存在。
		 */
		fc_old_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
									 CStringGetDatum(fc_multirangeTypeName),
									 ObjectIdGetDatum(fc_multirangeNamespace));

		/*
		 * 如果它不是一个外壳，看看它是否是一个自动生成的数组类型，
		 * 如果是的话将其重命名以避免冲突。
		 */
		if (OidIsValid(fc_old_typoid) && get_typisdefined(fc_old_typoid))
		{
			if (!moveArrayTypeName(fc_old_typoid, fc_multirangeTypeName, fc_multirangeNamespace))
				ereport(ERROR,
						(errcode(ERRCODE_DUPLICATE_OBJECT),
						 errmsg("type \"%s\" already exists", fc_multirangeTypeName)));
		}
	}
	else
	{
		/* 自动生成多范围名称 */
		fc_multirangeNamespace = fc_typeNamespace;
		fc_multirangeTypeName = makeMultirangeTypeName(fc_typeName, fc_multirangeNamespace);
	}

	fc_mltrngaddress =
		TypeCreate(fc_multirangeOid,	/* 强制分配这个类型的 OID */
				   fc_multirangeTypeName,	/* 类型名称 */
				   fc_multirangeNamespace, /* 名称空间 */
				   InvalidOid,	/* 关系 oid（在这里不适用） */
				   0,			/* 关系类型（同上） */
				   GetUserId(), /* 所有者的 ID */
				   -1,			/* 内部大小（始终为 varlena） */
				   TYPTYPE_MULTIRANGE,	/* 类型-类型 (多范围类型) */
				   TYPCATEGORY_RANGE,	/* 类型类别（范围类型） */
				   false,		/* 多范围类型永远不会被优先 */
				   DEFAULT_TYPDELIM,	/* 数组元素分隔符 */
				   F_MULTIRANGE_IN, /* 输入过程 */
				   F_MULTIRANGE_OUT,	/* 输出过程 */
				   F_MULTIRANGE_RECV,	/* 接收过程 */
				   F_MULTIRANGE_SEND,	/* 发送过程 */
				   InvalidOid,	/* typmodin 过程 - 无 */
				   InvalidOid,	/* typmodout 过程 - 无 */
				   F_MULTIRANGE_TYPANALYZE, /* 分析过程 */
				   InvalidOid,	/* 下标过程 - 无 */
				   InvalidOid,	/* 元素类型 ID - 无 */
				   false,		/* 这不是一个数组类型 */
				   fc_multirangeArrayOid,	/* 我们即将创建的数组类型 */
				   InvalidOid,	/* 基本类型 ID（仅适用于域） */
				   NULL,		/* 从不为默认类型值 */
				   NULL,		/* 也没有可用的二进制形式 */
				   false,		/* 从不按值传递 */
				   fc_alignment,	/* 对齐方式 */
				   'x',			/* TOAST 策略（始终为扩展） */
				   -1,			/* typMod（仅适用于域） */
				   0,			/* typbasetype 的数组维度 */
				   false,		/* 类型不为 NULL */
				   InvalidOid); /* 类型的排序规则（范围类型从不具有） */
	Assert(fc_multirangeOid == fc_mltrngaddress.objectId);

	/* 在 pg_range 中创建条目 */
	RangeCreate(fc_typoid, fc_rangeSubtype, fc_rangeCollation, fc_rangeSubOpclass,
				fc_rangeCanonical, fc_rangeSubtypeDiff, fc_multirangeOid);

	/*
	 * 创建与之对应的数组类型。
	 */
	fc_rangeArrayName = makeArrayTypeName(fc_typeName, fc_typeNamespace);

	TypeCreate(fc_rangeArrayOid,	/* 强制分配这个类型的 OID */
			   fc_rangeArrayName,	/* 类型名称 */
			   fc_typeNamespace,	/* 名称空间 */
			   InvalidOid,		/* 关系 oid（在这里不适用） */
			   0,				/* 关系类型（同上） */
			   GetUserId(),		/* 所有者的 ID */
			   -1,				/* 内部大小（始终为 varlena） */
			   TYPTYPE_BASE,	/* 类型-类型（基本类型） */
			   TYPCATEGORY_ARRAY,	/* 类型类别（数组） */
			   false,			/* 数组类型从不优选 */
			   DEFAULT_TYPDELIM,	/* 数组元素分隔符 */
			   F_ARRAY_IN,		/* 输入过程 */
			   F_ARRAY_OUT,		/* 输出过程 */
			   F_ARRAY_RECV,	/* 接收过程 */
			   F_ARRAY_SEND,	/* 发送过程 */
			   InvalidOid,		/* typmodin 过程 - 无 */
			   InvalidOid,		/* typmodout 过程 - 无 */
			   F_ARRAY_TYPANALYZE,	/* 分析过程 */
			   F_ARRAY_SUBSCRIPT_HANDLER,	/* 数组下标过程 */
			   fc_typoid,			/* 元素类型 ID */
			   true,			/* 是的，这是一种数组类型 */
			   InvalidOid,		/* 无进一步的数组类型 */
			   InvalidOid,		/* 基本类型 ID */
			   NULL,			/* 从不为默认类型值 */
			   NULL,			/* 二进制默认值也不被发送 */
			   false,			/* 从不按值传递 */
			   fc_alignment,		/* 对齐 - 与范围相同 */
			   TYPSTORAGE_EXTENDED, /* ARRAY 始终可压缩 */
			   -1,				/* typMod（仅适用于域） */
			   0,				/* typbasetype 的数组维度 */
			   false,			/* 类型不为 NULL */
			   InvalidOid);		/* typcollation */

	pfree(fc_rangeArrayName);

	/* 创建多范围的数组类型 */

	fc_multirangeArrayName = makeArrayTypeName(fc_multirangeTypeName, fc_typeNamespace);

	TypeCreate(fc_multirangeArrayOid,	/* 强制分配这个类型的 OID */
			   fc_multirangeArrayName, /* 类型名称 */
			   fc_multirangeNamespace, /* 名称空间 */
			   InvalidOid,		/* 关系 oid（在这里不适用） */
			   0,				/* 关系类型（同上） */
			   GetUserId(),		/* 所有者的 ID */
			   -1,				/* 内部大小（始终为 varlena） */
			   TYPTYPE_BASE,	/* 类型-类型（基本类型） */
			   TYPCATEGORY_ARRAY,	/* 类型类别（数组） */
			   false,			/* 数组类型从不优选 */
			   DEFAULT_TYPDELIM,	/* 数组元素分隔符 */
			   F_ARRAY_IN,		/* 输入过程 */
			   F_ARRAY_OUT,		/* 输出过程 */
			   F_ARRAY_RECV,	/* 接收过程 */
			   F_ARRAY_SEND,	/* 发送过程 */
			   InvalidOid,		/* typmodin 过程 - 无 */
			   InvalidOid,		/* typmodout 过程 - 无 */
			   F_ARRAY_TYPANALYZE,	/* 分析过程 */
			   F_ARRAY_SUBSCRIPT_HANDLER,	/* 数组下标过程 */
			   fc_multirangeOid,	/* 元素类型 ID */
			   true,			/* 是的，这是一种数组类型 */
			   InvalidOid,		/* 无进一步的数组类型 */
			   InvalidOid,		/* 基本类型 ID */
			   NULL,			/* 从不为默认类型值 */
			   NULL,			/* 二进制默认值也不被发送 */
			   false,			/* 从不按值传递 */
			   fc_alignment,		/* 对齐 - 与范围相同 */
			   'x',				/* ARRAY 始终可压缩 */
			   -1,				/* typMod（仅适用于域） */
			   0,				/* typbasetype 的数组维度 */
			   false,			/* 类型不为 NULL */
			   InvalidOid);		/* typcollation */

	/* 并为该范围类型创建构造函数 */
	fc_makeRangeConstructors(fc_typeName, fc_typeNamespace, fc_typoid, fc_rangeSubtype);
	fc_makeMultirangeConstructors(fc_multirangeTypeName, fc_typeNamespace,
							   fc_multirangeOid, fc_typoid, fc_rangeArrayOid,
							   &fc_castFuncOid);

	/* 创建从范围类型到其多范围类型的转换 */
	CastCreate(fc_typoid, fc_multirangeOid, fc_castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);

	pfree(fc_multirangeArrayName);

	return fc_address;
}

/*
 * 因为可能存在多个相同子类型的范围类型，
 * 所以无法仅从子类型唯一确定范围类型。因此，
 * 不可能定义多态构造函数；我们必须为每个范围类型显式生成新的构造函数。
 *
 * 我们实际上定义了 4 个函数，具有 0 到 3 个参数。这只是
 * 为用户提供更多便利。
 */
static void fc_makeRangeConstructors(const char *fc_name, Oid fc_namespace,
					  Oid fc_rangeOid, Oid fc_subtype)
{
	static const char *const fc_prosrc[2] = {"range_constructor2",
	"range_constructor3"};
	static const int fc_pronargs[2] = {2, 3};

	Oid			fc_constructorArgTypes[3];
	ObjectAddress fc_myself,
				fc_referenced;
	int			fc_i;

	fc_constructorArgTypes[0] = fc_subtype;
	fc_constructorArgTypes[1] = fc_subtype;
	fc_constructorArgTypes[2] = TEXTOID;

	fc_referenced.classId = TypeRelationId;
	fc_referenced.objectId = fc_rangeOid;
	fc_referenced.objectSubId = 0;

	for (fc_i = 0; fc_i < lengthof(fc_prosrc); fc_i++)
	{
		oidvector  *fc_constructorArgTypesVector;

		fc_constructorArgTypesVector = buildoidvector(fc_constructorArgTypes,
												   fc_pronargs[fc_i]);

		fc_myself = ProcedureCreate(fc_name,	/* 名称：与范围类型相同 */
								 fc_namespace, /* 名称空间 */
								 false, /* 替换 */
								 false, /* 返回集合 */
								 fc_rangeOid,	/* 返回类型 */
								 BOOTSTRAP_SUPERUSERID, /* proowner */
								 INTERNALlanguageId,	/* 语言 */
								 F_FMGR_INTERNAL_VALIDATOR, /* 语言验证器 */
								 fc_prosrc[fc_i], /* prosrc */
								 NULL,	/* probin */
								 NULL,	/* prosqlbody */
								 PROKIND_FUNCTION,
								 false, /* security_definer */
								 false, /* leakproof */
								 false, /* isStrict */
								 PROVOLATILE_IMMUTABLE, /* volatility */
								 PROPARALLEL_SAFE,	/* parallel safety */
								 fc_constructorArgTypesVector, /* parameterTypes */
								 PointerGetDatum(NULL), /* allParameterTypes */
								 PointerGetDatum(NULL), /* parameterModes */
								 PointerGetDatum(NULL), /* parameterNames */
								 NIL,	/* parameterDefaults */
								 PointerGetDatum(NULL), /* trftypes */
								 PointerGetDatum(NULL), /* proconfig */
								 InvalidOid,	/* prosupport */
								 1.0,	/* procost */
								 0.0);	/* prorows */

		/*
		 * 使构造函数在内部依赖于范围类型，以便当类型被删除时
		 * 他们可以悄无声息地消失。请注意，
		 * pg_dump 依赖于此选择以避免导出构造函数。
		 */
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_INTERNAL);
	}
}

/*
 * 我们为每个范围类型制作一个单独的多范围构造函数，
 * 以便其名称可以包含基本类型，像范围构造函数一样。
 * 如果我们有一个 anyrangearray 多态类型，我们可以在这里使用它，
 * 但由于每种类型都有自己的构造函数名称，所以没有必要。
 *
 * 将 castFuncOid 设置为可用于从范围转换为多范围的新构造函数的 oid。
 */
static void fc_makeMultirangeConstructors(const char *fc_name, Oid fc_namespace,
						   Oid fc_multirangeOid, Oid fc_rangeOid, Oid fc_rangeArrayOid,
						   Oid *fc_castFuncOid)
{
	ObjectAddress fc_myself,
				fc_referenced;
	oidvector  *fc_argtypes;
	Datum		fc_allParamTypes;
	ArrayType  *fc_allParameterTypes;
	Datum		fc_paramModes;
	ArrayType  *fc_parameterModes;

	fc_referenced.classId = TypeRelationId;
	fc_referenced.objectId = fc_multirangeOid;
	fc_referenced.objectSubId = 0;

	/* 0-参数构造函数 - 用于空多范围 */
	fc_argtypes = buildoidvector(NULL, 0);
	fc_myself = ProcedureCreate(fc_name,	/* 名称：与多范围类型相同 */
							 fc_namespace,
							 false, /* 替换 */
							 false, /* 返回集合 */
							 fc_multirangeOid, /* 返回类型 */
							 BOOTSTRAP_SUPERUSERID, /* proowner */
							 INTERNALlanguageId,	/* 语言 */
							 F_FMGR_INTERNAL_VALIDATOR,
							 "multirange_constructor0", /* prosrc */
							 NULL,	/* probin */
							 NULL,	/* prosqlbody */
							 PROKIND_FUNCTION,
							 false, /* security_definer */
							 false, /* leakproof */
							 true,	/* isStrict */
							 PROVOLATILE_IMMUTABLE, /* volatility */
							 PROPARALLEL_SAFE,	/* parallel safety */
							 fc_argtypes,	/* parameterTypes */
							 PointerGetDatum(NULL), /* allParameterTypes */
							 PointerGetDatum(NULL), /* parameterModes */
							 PointerGetDatum(NULL), /* parameterNames */
							 NIL,	/* parameterDefaults */
							 PointerGetDatum(NULL), /* trftypes */
							 PointerGetDatum(NULL), /* proconfig */
							 InvalidOid,	/* prosupport */
							 1.0,	/* procost */
							 0.0);	/* prorows */

	/*
	 * 使构造函数内部依赖于多范围类型，以便在类型被删除时能够静默消失。注意 pg_dump 依赖于这一选择以避免转储构造函数。
	 */
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_INTERNAL);
	pfree(fc_argtypes);

	/*
	 * 1-参数构造函数 - 用于类型转换
	 *
	 * 理论上我们不应该同时需要这个和可变参数（n-参数）构造函数，但有一个单独的 1-参数函数让我们能够定义类型转换。
	 */
	fc_argtypes = buildoidvector(&fc_rangeOid, 1);
	fc_myself = ProcedureCreate(fc_name,	/* 名称：与多范围类型相同 */
							 fc_namespace,
							 false, /* 替换 */
							 false, /* 返回集合 */
							 fc_multirangeOid, /* 返回类型 */
							 BOOTSTRAP_SUPERUSERID, /* proowner */
							 INTERNALlanguageId,	/* 语言 */
							 F_FMGR_INTERNAL_VALIDATOR,
							 "multirange_constructor1", /* prosrc */
							 NULL,	/* probin */
							 NULL,	/* prosqlbody */
							 PROKIND_FUNCTION,
							 false, /* security_definer */
							 false, /* leakproof */
							 true,	/* isStrict */
							 PROVOLATILE_IMMUTABLE, /* volatility */
							 PROPARALLEL_SAFE,	/* parallel safety */
							 fc_argtypes,	/* parameterTypes */
							 PointerGetDatum(NULL), /* allParameterTypes */
							 PointerGetDatum(NULL), /* parameterModes */
							 PointerGetDatum(NULL), /* parameterNames */
							 NIL,	/* parameterDefaults */
							 PointerGetDatum(NULL), /* trftypes */
							 PointerGetDatum(NULL), /* proconfig */
							 InvalidOid,	/* prosupport */
							 1.0,	/* procost */
							 0.0);	/* prorows */
	/* 同上 */
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_INTERNAL);
	pfree(fc_argtypes);
	*fc_castFuncOid = fc_myself.objectId;

	/* n-参数构造函数 - 可变参数 */
	fc_argtypes = buildoidvector(&fc_rangeArrayOid, 1);
	fc_allParamTypes = ObjectIdGetDatum(fc_rangeArrayOid);
	fc_allParameterTypes = construct_array(&fc_allParamTypes,
										1, OIDOID,
										sizeof(Oid), true, TYPALIGN_INT);
	fc_paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
	fc_parameterModes = construct_array(&fc_paramModes, 1, CHAROID,
									 1, true, TYPALIGN_CHAR);
	fc_myself = ProcedureCreate(fc_name,	/* 名称：与多范围类型相同 */
							 fc_namespace,
							 false, /* 替换 */
							 false, /* 返回集合 */
							 fc_multirangeOid, /* 返回类型 */
							 BOOTSTRAP_SUPERUSERID, /* proowner */
							 INTERNALlanguageId,	/* 语言 */
							 F_FMGR_INTERNAL_VALIDATOR,
							 "multirange_constructor2", /* prosrc */
							 NULL,	/* probin */
							 NULL,	/* prosqlbody */
							 PROKIND_FUNCTION,
							 false, /* security_definer */
							 false, /* leakproof */
							 true,	/* isStrict */
							 PROVOLATILE_IMMUTABLE, /* volatility */
							 PROPARALLEL_SAFE,	/* parallel safety */
							 fc_argtypes,	/* parameterTypes */
							 PointerGetDatum(fc_allParameterTypes),	/* allParameterTypes */
							 PointerGetDatum(fc_parameterModes),	/* parameterModes */
							 PointerGetDatum(NULL), /* parameterNames */
							 NIL,	/* parameterDefaults */
							 PointerGetDatum(NULL), /* trftypes */
							 PointerGetDatum(NULL), /* proconfig */
							 InvalidOid,	/* prosupport */
							 1.0,	/* procost */
							 0.0);	/* prorows */
	/* 同上 */
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_INTERNAL);
	pfree(fc_argtypes);
	pfree(fc_allParameterTypes);
	pfree(fc_parameterModes);
}

/*
 * 为一个类型找到合适的 I/O 和其他支持函数。
 *
 * typeOid 是类型的 OID（如果仅作为一个壳类型存在，它将已经存在）。
 */

static Oid fc_findTypeInputFunction(List *fc_procname, Oid fc_typeOid)
{
	Oid			fc_argList[3];
	Oid			fc_procOid;
	Oid			fc_procOid2;

	/*
	 * 输入函数可以接受一个类型为 CSTRING 的单个参数，或三个参数（字符串，typioparam OID，typmod）。如果两种形式同时存在，则会抱怨模糊性。
	 */
	fc_argList[0] = CSTRINGOID;
	fc_argList[1] = OIDOID;
	fc_argList[2] = INT4OID;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	fc_procOid2 = LookupFuncName(fc_procname, 3, fc_argList, true);
	if (OidIsValid(fc_procOid))
	{
		if (OidIsValid(fc_procOid2))
			ereport(ERROR,
					(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
					 errmsg("type input function %s has multiple matches",
							NameListToString(fc_procname))));
	}
	else
	{
		fc_procOid = fc_procOid2;
		/* 如果未找到，则在错误消息中引用 1-参数签名 */
		if (!OidIsValid(fc_procOid))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_FUNCTION),
					 errmsg("function %s does not exist",
							func_signature_string(fc_procname, 1, NIL, fc_argList))));
	}

	/* 输入函数必须返回目标类型。 */
	if (get_func_rettype(fc_procOid) != fc_typeOid)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type input function %s must return type %s",
						NameListToString(fc_procname), format_type_be(fc_typeOid))));

	/*
	 * 如果类型的任何 I/O 函数被标记为波动，打印警告。
	 * 有一个普遍的假设是 I/O 函数是稳定或不可变的；这使我们能将 record_in/record_out 标记为稳定而不是波动。理想情况下，我们在这里应该抛出错误而不仅仅是警告；但由于这个检查是自 9.5 版本以来的新功能，并且波动标记可能只是遗漏错误而不是函数行为的真实指示，因此我们现在暂时将其视为警告。
	 */
	if (func_volatile(fc_procOid) == PROVOLATILE_VOLATILE)
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type input function %s should not be volatile",
						NameListToString(fc_procname))));

	return fc_procOid;
}

static Oid fc_findTypeOutputFunction(List *fc_procname, Oid fc_typeOid)
{
	Oid			fc_argList[1];
	Oid			fc_procOid;

	/*
	 * 输出函数总是接受一个类型的单个参数并返回 cstring。
	 */
	fc_argList[0] = fc_typeOid;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != CSTRINGOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type output function %s must return type %s",
						NameListToString(fc_procname), "cstring")));

	/* 目前仅仅是一个警告，依照 findTypeInputFunction 中的评论 */
	if (func_volatile(fc_procOid) == PROVOLATILE_VOLATILE)
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type output function %s should not be volatile",
						NameListToString(fc_procname))));

	return fc_procOid;
}

static Oid fc_findTypeReceiveFunction(List *fc_procname, Oid fc_typeOid)
{
	Oid			fc_argList[3];
	Oid			fc_procOid;
	Oid			fc_procOid2;

	/*
	 * 接收函数可以接受一个类型为 INTERNAL 的参数，或者三个参数（internal，typioparam OID，typmod）。如果存在两种形式则发出模糊性警告。
	 */
	fc_argList[0] = INTERNALOID;
	fc_argList[1] = OIDOID;
	fc_argList[2] = INT4OID;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	fc_procOid2 = LookupFuncName(fc_procname, 3, fc_argList, true);
	if (OidIsValid(fc_procOid))
	{
		if (OidIsValid(fc_procOid2))
			ereport(ERROR,
					(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
					 errmsg("type receive function %s has multiple matches",
							NameListToString(fc_procname))));
	}
	else
	{
		fc_procOid = fc_procOid2;
		/* 如果未找到，则在错误消息中引用 1-参数签名 */
		if (!OidIsValid(fc_procOid))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_FUNCTION),
					 errmsg("function %s does not exist",
							func_signature_string(fc_procname, 1, NIL, fc_argList))));
	}

	/* 接收函数必须返回目标类型。 */
	if (get_func_rettype(fc_procOid) != fc_typeOid)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type receive function %s must return type %s",
						NameListToString(fc_procname), format_type_be(fc_typeOid))));

	/* 目前仅仅是一个警告，依照 findTypeInputFunction 中的评论 */
	if (func_volatile(fc_procOid) == PROVOLATILE_VOLATILE)
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type receive function %s should not be volatile",
						NameListToString(fc_procname))));

	return fc_procOid;
}

static Oid fc_findTypeSendFunction(List *fc_procname, Oid fc_typeOid)
{
	Oid			fc_argList[1];
	Oid			fc_procOid;

	/*
	 * 发送函数始终接受一个类型参数并返回 bytea。
	 */
	fc_argList[0] = fc_typeOid;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != BYTEAOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type send function %s must return type %s",
						NameListToString(fc_procname), "bytea")));

	/* 目前仅仅是一个警告，依照 findTypeInputFunction 中的评论 */
	if (func_volatile(fc_procOid) == PROVOLATILE_VOLATILE)
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type send function %s should not be volatile",
						NameListToString(fc_procname))));

	return fc_procOid;
}

static Oid fc_findTypeTypmodinFunction(List *fc_procname)
{
	Oid			fc_argList[1];
	Oid			fc_procOid;

	/*
	 * typmodin 函数始终接受一个 cstring[] 参数并返回 int4。
	 */
	fc_argList[0] = CSTRINGARRAYOID;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != INT4OID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("typmod_in function %s must return type %s",
						NameListToString(fc_procname), "integer")));

	/* 目前仅仅是一个警告，依照 findTypeInputFunction 中的评论 */
	if (func_volatile(fc_procOid) == PROVOLATILE_VOLATILE)
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type modifier input function %s should not be volatile",
						NameListToString(fc_procname))));

	return fc_procOid;
}

static Oid fc_findTypeTypmodoutFunction(List *fc_procname)
{
	Oid			fc_argList[1];
	Oid			fc_procOid;

	/*
	 * typmodout 函数始终接受一个 int4 参数并返回 cstring。
	 */
	fc_argList[0] = INT4OID;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != CSTRINGOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("typmod_out function %s must return type %s",
						NameListToString(fc_procname), "cstring")));

	/* 目前仅仅是一个警告，依照 findTypeInputFunction 中的评论 */
	if (func_volatile(fc_procOid) == PROVOLATILE_VOLATILE)
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type modifier output function %s should not be volatile",
						NameListToString(fc_procname))));

	return fc_procOid;
}

static Oid fc_findTypeAnalyzeFunction(List *fc_procname, Oid fc_typeOid)
{
	Oid			fc_argList[1];
	Oid			fc_procOid;

	/*
	 * 分析函数始终接受一个 INTERNAL 参数并返回 bool。
	 */
	fc_argList[0] = INTERNALOID;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != BOOLOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type analyze function %s must return type %s",
						NameListToString(fc_procname), "boolean")));

	return fc_procOid;
}

static Oid fc_findTypeSubscriptingFunction(List *fc_procname, Oid fc_typeOid)
{
	Oid			fc_argList[1];
	Oid			fc_procOid;

	/*
	 * 下标支持函数始终接受一个 INTERNAL 参数并返回 INTERNAL。（该参数未使用，但我们必须保留它以维护类型安全。）
	 */
	fc_argList[0] = INTERNALOID;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);
	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != INTERNALOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("type subscripting function %s must return type %s",
						NameListToString(fc_procname), "internal")));

	/*
	 * 我们不允许显式选择 array_subscript_handler()，因为该函数只能应用于自动生成的数组类型。
	 */
	if (fc_procOid == F_ARRAY_SUBSCRIPT_HANDLER)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("user-defined types cannot use subscripting function %s",
						NameListToString(fc_procname))));

	return fc_procOid;
}

/*
 * 查找适合范围类型的支持函数和操作类。
 */

/*
 * 查找子类型的命名 btree 操作类，或在 opcname 为 NIL 时使用默认 btree 操作类。
 */
static Oid fc_findRangeSubOpclass(List *fc_opcname, Oid fc_subtype)
{
	Oid			fc_opcid;
	Oid			fc_opInputType;

	if (fc_opcname != NIL)
	{
		fc_opcid = get_opclass_oid(BTREE_AM_OID, fc_opcname, false);

		/*
		 * 验证操作类是否接受此数据类型。注意我们将接受二进制兼容性。
		 */
		fc_opInputType = get_opclass_input_type(fc_opcid);
		if (!IsBinaryCoercible(fc_subtype, fc_opInputType))
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("operator class \"%s\" does not accept data type %s",
							NameListToString(fc_opcname),
							format_type_be(fc_subtype))));
	}
	else
	{
		fc_opcid = GetDefaultOpClass(fc_subtype, BTREE_AM_OID);
		if (!OidIsValid(fc_opcid))
		{
			/* 错误信息与 ResolveOpClass 中的错误信息完全相同 */
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("data type %s has no default operator class for access method \"%s\"",
							format_type_be(fc_subtype), "btree"),
					 errhint("You must specify an operator class for the range type or define a default operator class for the subtype.")));
		}
	}

	return fc_opcid;
}

static Oid fc_findRangeCanonicalFunction(List *fc_procname, Oid fc_typeOid)
{
	Oid			fc_argList[1];
	Oid			fc_procOid;
	AclResult	fc_aclresult;

	/*
	 * 范围规范化函数必须接受和返回范围类型，并且必须是不可变的。
	 */
	fc_argList[0] = fc_typeOid;

	fc_procOid = LookupFuncName(fc_procname, 1, fc_argList, true);

	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != fc_typeOid)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("range canonical function %s must return range type",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	if (func_volatile(fc_procOid) != PROVOLATILE_IMMUTABLE)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("range canonical function %s must be immutable",
						func_signature_string(fc_procname, 1, NIL, fc_argList))));

	/* 此外，范围类型的创建者必须有权限调用该函数 */
	fc_aclresult = pg_proc_aclcheck(fc_procOid, GetUserId(), ACL_EXECUTE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_FUNCTION, get_func_name(fc_procOid));

	return fc_procOid;
}

static Oid fc_findRangeSubtypeDiffFunction(List *fc_procname, Oid fc_subtype)
{
	Oid			fc_argList[2];
	Oid			fc_procOid;
	AclResult	fc_aclresult;

	/*
	 * 范围子类型 diff 函数必须接受两个子类型参数，必须返回 float8，并且必须是不可变的。
	 */
	fc_argList[0] = fc_subtype;
	fc_argList[1] = fc_subtype;

	fc_procOid = LookupFuncName(fc_procname, 2, fc_argList, true);

	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procname, 2, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != FLOAT8OID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("range subtype diff function %s must return type %s",
						func_signature_string(fc_procname, 2, NIL, fc_argList),
						"double precision")));

	if (func_volatile(fc_procOid) != PROVOLATILE_IMMUTABLE)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("range subtype diff function %s must be immutable",
						func_signature_string(fc_procname, 2, NIL, fc_argList))));

	/* 此外，范围类型的创建者必须有权限调用该函数 */
	fc_aclresult = pg_proc_aclcheck(fc_procOid, GetUserId(), ACL_EXECUTE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_FUNCTION, get_func_name(fc_procOid));

	return fc_procOid;
}

/*
 *	AssignTypeArrayOid
 *
 *	预分配类型的数组 OID 以便在 pg_type.typarray 中使用
 */
Oid AssignTypeArrayOid(void)
{
	Oid			fc_type_array_oid;

	/* 是否使用二进制升级覆盖 pg_type.typarray？ */
	if (IsBinaryUpgrade)
	{
		if (!OidIsValid(binary_upgrade_next_array_pg_type_oid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("pg_type array OID value not set when in binary upgrade mode")));

		fc_type_array_oid = binary_upgrade_next_array_pg_type_oid;
		binary_upgrade_next_array_pg_type_oid = InvalidOid;
	}
	else
	{
		Relation	fc_pg_type = table_open(TypeRelationId, AccessShareLock);

		fc_type_array_oid = GetNewOidWithIndex(fc_pg_type, TypeOidIndexId,
											Anum_pg_type_oid);
		table_close(fc_pg_type, AccessShareLock);
	}

	return fc_type_array_oid;
}

/*
 *	AssignTypeMultirangeOid
 *
 *	预分配范围类型的多范围 OID 以便在 pg_type.oid 中使用
 */
Oid AssignTypeMultirangeOid(void)
{
	Oid			fc_type_multirange_oid;

	/* 是否使用二进制升级覆盖 pg_type.oid？ */
	if (IsBinaryUpgrade)
	{
		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));

		fc_type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
	}
	else
	{
		Relation	fc_pg_type = table_open(TypeRelationId, AccessShareLock);

		fc_type_multirange_oid = GetNewOidWithIndex(fc_pg_type, TypeOidIndexId,
												 Anum_pg_type_oid);
		table_close(fc_pg_type, AccessShareLock);
	}

	return fc_type_multirange_oid;
}

/*
 *	AssignTypeMultirangeArrayOid
 *
 *	预分配范围类型的多范围数组 OID 以便在 pg_type.typarray 中使用
 */
Oid AssignTypeMultirangeArrayOid(void)
{
	Oid			fc_type_multirange_array_oid;

	/* 是否使用二进制升级覆盖 pg_type.oid？ */
	if (IsBinaryUpgrade)
	{
		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));

		fc_type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
	}
	else
	{
		Relation	fc_pg_type = table_open(TypeRelationId, AccessShareLock);

		fc_type_multirange_array_oid = GetNewOidWithIndex(fc_pg_type, TypeOidIndexId,
													   Anum_pg_type_oid);
		table_close(fc_pg_type, AccessShareLock);
	}

	return fc_type_multirange_array_oid;
}


/*-------------------------------------------------------------------
 * DefineCompositeType
 *
 * 创建一个复合类型关系。
 * `DefineRelation' 完成所有工作，我们只需提供正确的参数！
 *
 * 如果关系已存在，则 'DefineRelation' 将中止事务...
 *
 * 返回类型是新类型的对象地址。
 *-------------------------------------------------------------------
 */
ObjectAddress DefineCompositeType(RangeVar *fc_typevar, List *fc_coldeflist)
{
	CreateStmt *fc_createStmt = makeNode(CreateStmt);
	Oid			fc_old_type_oid;
	Oid			fc_typeNamespace;
	ObjectAddress fc_address;

	/*
	 * 现在设置键/继承等的参数。所有这些对于复合类型来说
	 * 都不重要...
	 */
	fc_createStmt->relation = fc_typevar;
	fc_createStmt->tableElts = fc_coldeflist;
	fc_createStmt->inhRelations = NIL;
	fc_createStmt->constraints = NIL;
	fc_createStmt->options = NIL;
	fc_createStmt->oncommit = ONCOMMIT_NOOP;
	fc_createStmt->tablespacename = NULL;
	fc_createStmt->if_not_exists = false;

	/*
	 * 检查与现有类型名称的冲突。如果存在并且
	 * 是自动生成的数组，我们可以将其重命名以避免冲突。这个
	 * 检查主要是为了获得关于“类型”的更好错误消息，
	 * 而不是下面关于“关系”的消息。
	 */
	fc_typeNamespace = RangeVarGetAndCheckCreationNamespace(fc_createStmt->relation,
														 NoLock, NULL);
	RangeVarAdjustRelationPersistence(fc_createStmt->relation, fc_typeNamespace);
	fc_old_type_oid =
		GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
						CStringGetDatum(fc_createStmt->relation->relname),
						ObjectIdGetDatum(fc_typeNamespace));
	if (OidIsValid(fc_old_type_oid))
	{
		if (!moveArrayTypeName(fc_old_type_oid, fc_createStmt->relation->relname, fc_typeNamespace))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists", fc_createStmt->relation->relname)));
	}

	/*
	 * 最终创建关系。这也创建类型。
	 */
	DefineRelation(fc_createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &fc_address,
				   NULL);

	return fc_address;
}

/*
 * AlterDomainDefault
 *
 * 实现 ALTER DOMAIN SET/DROP DEFAULT 语句的例程。
 *
 * 返回被修改域的 ObjectAddress。
 */
ObjectAddress AlterDomainDefault(List *fc_names, Node *fc_defaultRaw)
{
	TypeName   *fc_typename;
	Oid			fc_domainoid;
	HeapTuple	fc_tup;
	ParseState *fc_pstate;
	Relation	fc_rel;
	char	   *fc_defaultValue;
	Node	   *fc_defaultExpr = NULL; /* 如果未指定默认值，则为 NULL */
	Datum		fc_new_record[Natts_pg_type];
	bool		fc_new_record_nulls[Natts_pg_type];
	bool		fc_new_record_repl[Natts_pg_type];
	HeapTuple	fc_newtuple;
	Form_pg_type fc_typTup;
	ObjectAddress fc_address;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);
	fc_domainoid = typenameTypeId(NULL, fc_typename);

	/* 在类型表中查找域 */
	fc_rel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(fc_domainoid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_domainoid);
	fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 检查它是否是域，并检查用户是否有 ALTER DOMAIN 的权限 */
	checkDomainOwner(fc_tup);

	/* 设置新元组 */
	MemSet(fc_new_record, (Datum) 0, sizeof(fc_new_record));
	MemSet(fc_new_record_nulls, false, sizeof(fc_new_record_nulls));
	MemSet(fc_new_record_repl, false, sizeof(fc_new_record_repl));

	/* 将新默认值存储到元组中 */
	if (fc_defaultRaw)
	{
		/* 为 transformExpr 创建一个虚拟 ParseState */
		fc_pstate = make_parsestate(NULL);

		/*
		 * 将 colDef->raw_expr 处理成一个表达式。注意：名称
		 * 仅用于错误消息
		 */
		fc_defaultExpr = cookDefault(fc_pstate, fc_defaultRaw,
								  fc_typTup->typbasetype,
								  fc_typTup->typtypmod,
								  NameStr(fc_typTup->typname),
								  0);

		/*
		 * 如果表达式只是一个 NULL 常量，我们将该命令
		 * 视为 ALTER ... DROP DEFAULT。 （但请参见
		 * 定义域中的相同测试的注释。）
		 */
		if (fc_defaultExpr == NULL ||
			(IsA(fc_defaultExpr, Const) && ((Const *) fc_defaultExpr)->constisnull))
		{
			/* 默认值为 NULL，删除它 */
			fc_defaultExpr = NULL;
			fc_new_record_nulls[Anum_pg_type_typdefaultbin - 1] = true;
			fc_new_record_repl[Anum_pg_type_typdefaultbin - 1] = true;
			fc_new_record_nulls[Anum_pg_type_typdefault - 1] = true;
			fc_new_record_repl[Anum_pg_type_typdefault - 1] = true;
		}
		else
		{
			/*
			 * 表达式必须作为 nodeToString 结果存储，但我们还要求
			 * 有效的文本表示（主要是为了方便 pg_dump）。
			 */
			fc_defaultValue = deparse_expression(fc_defaultExpr,
											  NIL, false, false);

			/*
			 * 形成带有新默认值的更新元组并写回。
			 */
			fc_new_record[Anum_pg_type_typdefaultbin - 1] = CStringGetTextDatum(nodeToString(fc_defaultExpr));

			fc_new_record_repl[Anum_pg_type_typdefaultbin - 1] = true;
			fc_new_record[Anum_pg_type_typdefault - 1] = CStringGetTextDatum(fc_defaultValue);
			fc_new_record_repl[Anum_pg_type_typdefault - 1] = true;
		}
	}
	else
	{
		/* ALTER ... DROP DEFAULT */
		fc_new_record_nulls[Anum_pg_type_typdefaultbin - 1] = true;
		fc_new_record_repl[Anum_pg_type_typdefaultbin - 1] = true;
		fc_new_record_nulls[Anum_pg_type_typdefault - 1] = true;
		fc_new_record_repl[Anum_pg_type_typdefault - 1] = true;
	}

	fc_newtuple = heap_modify_tuple(fc_tup, RelationGetDescr(fc_rel),
								 fc_new_record, fc_new_record_nulls,
								 fc_new_record_repl);

	CatalogTupleUpdate(fc_rel, &fc_tup->t_self, fc_newtuple);

	/* 重新构建依赖关系 */
	GenerateTypeDependencies(fc_newtuple,
							 fc_rel,
							 fc_defaultExpr,
							 NULL,	/* 不要随便使用 typacl */
							 0, /* 关系类型不适用 */
							 false, /* 域不是隐式数组 */
							 false, /* 也不是任何依赖类型 */
							 false, /* 不要涉及扩展成员资格 */
							 true); /* 我们确实需要重新构建依赖关系 */

	InvokeObjectPostAlterHook(TypeRelationId, fc_domainoid, 0);

	ObjectAddressSet(fc_address, TypeRelationId, fc_domainoid);

	/* 清理 */
	table_close(fc_rel, RowExclusiveLock);
	heap_freetuple(fc_newtuple);

	return fc_address;
}

/*
 * AlterDomainNotNull
 *
 * 实现 ALTER DOMAIN SET/DROP NOT NULL 语句的例程。
 *
 * 返回被修改域的 ObjectAddress。
 */
ObjectAddress AlterDomainNotNull(List *fc_names, bool fc_notNull)
{
	TypeName   *fc_typename;
	Oid			fc_domainoid;
	Relation	fc_typrel;
	HeapTuple	fc_tup;
	Form_pg_type fc_typTup;
	ObjectAddress fc_address = InvalidObjectAddress;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);
	fc_domainoid = typenameTypeId(NULL, fc_typename);

	/* 在类型表中查找域 */
	fc_typrel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(fc_domainoid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_domainoid);
	fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 检查它是否是域，并检查用户是否有 ALTER DOMAIN 的权限 */
	checkDomainOwner(fc_tup);

	/* 域是否已经设置为所需约束？ */
	if (fc_typTup->typnotnull == fc_notNull)
	{
		table_close(fc_typrel, RowExclusiveLock);
		return fc_address;
	}

	/* 添加 NOT NULL 约束需要检查现有列 */
	if (fc_notNull)
	{
		List	   *fc_rels;
		ListCell   *fc_rt;

		/* 获取基于此域的属性的关系列表 */
		/* ShareLock 足以防止并发数据更改 */

		fc_rels = fc_get_rels_with_domain(fc_domainoid, ShareLock);

		foreach(fc_rt, fc_rels)
		{
			RelToCheck *fc_rtc = (RelToCheck *) lfirst(fc_rt);
			Relation	fc_testrel = fc_rtc->rel;
			TupleDesc	fc_tupdesc = RelationGetDescr(fc_testrel);
			TupleTableSlot *fc_slot;
			TableScanDesc fc_scan;
			Snapshot	fc_snapshot;

			/* 扫描此关系中的所有元组 */
			fc_snapshot = RegisterSnapshot(GetLatestSnapshot());
			fc_scan = table_beginscan(fc_testrel, fc_snapshot, 0, NULL);
			fc_slot = table_slot_create(fc_testrel, NULL);
			while (table_scan_getnextslot(fc_scan, ForwardScanDirection, fc_slot))
			{
				int			fc_i;

				/* 测试属于该域的属性 */
				for (fc_i = 0; fc_i < fc_rtc->natts; fc_i++)
				{
					int			fc_attnum = fc_rtc->atts[fc_i];
					Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_attnum - 1);

					if (slot_attisnull(fc_slot, fc_attnum))
					{
						/*
						 * 原则上，这个错误的辅助信息应该是 errdatatype()，
						 * 但在实践中，errtablecol() 似乎更有用。由于
						 * 这段代码仅在 ALTER DOMAIN 命令中执行，
						 * 客户端应该已经知道相关的域是哪个。
						 */
						ereport(ERROR,
								(errcode(ERRCODE_NOT_NULL_VIOLATION),
								 errmsg("column \"%s\" of table \"%s\" contains null values",
										NameStr(fc_attr->attname),
										RelationGetRelationName(fc_testrel)),
								 errtablecol(fc_testrel, fc_attnum)));
					}
				}
			}
			ExecDropSingleTupleTableSlot(fc_slot);
			table_endscan(fc_scan);
			UnregisterSnapshot(fc_snapshot);

			/* 处理完后关闭每个关系，但保持锁定 */
			table_close(fc_testrel, NoLock);
		}
	}

	/*
	 * 可以更新pg_type行。我们可以在typTup上随意修改，因为这是一个副本。
	 */
	fc_typTup->typnotnull = fc_notNull;

	CatalogTupleUpdate(fc_typrel, &fc_tup->t_self, fc_tup);

	InvokeObjectPostAlterHook(TypeRelationId, fc_domainoid, 0);

	ObjectAddressSet(fc_address, TypeRelationId, fc_domainoid);

	/* 清理 */
	heap_freetuple(fc_tup);
	table_close(fc_typrel, RowExclusiveLock);

	return fc_address;
}

/*
 * AlterDomainDropConstraint
 *
 * 实现ALTER DOMAIN DROP CONSTRAINT语句
 *
 * 返回修改后的域的ObjectAddress。
 */
ObjectAddress AlterDomainDropConstraint(List *fc_names, const char *fc_constrName,
						  DropBehavior fc_behavior, bool fc_missing_ok)
{
	TypeName   *fc_typename;
	Oid			fc_domainoid;
	HeapTuple	fc_tup;
	Relation	fc_rel;
	Relation	fc_conrel;
	SysScanDesc fc_conscan;
	ScanKeyData fc_skey[3];
	HeapTuple	fc_contup;
	bool		fc_found = false;
	ObjectAddress fc_address;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);
	fc_domainoid = typenameTypeId(NULL, fc_typename);

	/* 在类型表中查找域 */
	fc_rel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(fc_domainoid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_domainoid);

	/* 检查它是否是域，并检查用户是否有 ALTER DOMAIN 的权限 */
	checkDomainOwner(fc_tup);

	/* 在pg_constraint关系上获取适当的锁定 */
	fc_conrel = table_open(ConstraintRelationId, RowExclusiveLock);

	/* 查找并删除目标约束 */
	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_domainoid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_constrName));

	fc_conscan = systable_beginscan(fc_conrel, ConstraintRelidTypidNameIndexId, true,
								 NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if ((fc_contup = systable_getnext(fc_conscan)) != NULL)
	{
		ObjectAddress fc_conobj;

		fc_conobj.classId = ConstraintRelationId;
		fc_conobj.objectId = ((Form_pg_constraint) GETSTRUCT(fc_contup))->oid;
		fc_conobj.objectSubId = 0;

		performDeletion(&fc_conobj, fc_behavior, 0);
		fc_found = true;
	}

	/* 扫描后清理 */
	systable_endscan(fc_conscan);
	table_close(fc_conrel, RowExclusiveLock);

	if (!fc_found)
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("constraint \"%s\" of domain \"%s\" does not exist",
							fc_constrName, TypeNameToString(fc_typename))));
		else
			ereport(NOTICE,
					(errmsg("constraint \"%s\" of domain \"%s\" does not exist, skipping",
							fc_constrName, TypeNameToString(fc_typename))));
	}

	/*
	 * 我们必须发送一个sinval消息给该域，以确保任何依赖计划都被重建。由于此命令不会更改域的pg_type行，因此这不会自动发生；手动执行。
	 */
	CacheInvalidateHeapTuple(fc_rel, fc_tup, NULL);

	ObjectAddressSet(fc_address, TypeRelationId, fc_domainoid);

	/* 清理 */
	table_close(fc_rel, RowExclusiveLock);

	return fc_address;
}

/*
 * AlterDomainAddConstraint
 *
 * 实现ALTER DOMAIN .. ADD CONSTRAINT语句。
 */
ObjectAddress AlterDomainAddConstraint(List *fc_names, Node *fc_newConstraint,
						 ObjectAddress *fc_constrAddr)
{
	TypeName   *fc_typename;
	Oid			fc_domainoid;
	Relation	fc_typrel;
	HeapTuple	fc_tup;
	Form_pg_type fc_typTup;
	Constraint *fc_constr;
	char	   *fc_ccbin;
	ObjectAddress fc_address;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);
	fc_domainoid = typenameTypeId(NULL, fc_typename);

	/* 在类型表中查找域 */
	fc_typrel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(fc_domainoid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_domainoid);
	fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 检查它是否是域，并检查用户是否有 ALTER DOMAIN 的权限 */
	checkDomainOwner(fc_tup);

	if (!IsA(fc_newConstraint, Constraint))
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_newConstraint));

	fc_constr = (Constraint *) fc_newConstraint;

	switch (fc_constr->contype)
	{
		case CONSTR_CHECK:
			/* 下面处理 */
			break;

		case CONSTR_UNIQUE:
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("unique constraints not possible for domains")));
			break;

		case CONSTR_PRIMARY:
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("primary key constraints not possible for domains")));
			break;

		case CONSTR_EXCLUSION:
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("exclusion constraints not possible for domains")));
			break;

		case CONSTR_FOREIGN:
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("foreign key constraints not possible for domains")));
			break;

		case CONSTR_ATTR_DEFERRABLE:
		case CONSTR_ATTR_NOT_DEFERRABLE:
		case CONSTR_ATTR_DEFERRED:
		case CONSTR_ATTR_IMMEDIATE:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("specifying constraint deferrability not supported for domains")));
			break;

		default:
			elog(ERROR, "unrecognized constraint subtype: %d",
				 (int) fc_constr->contype);
			break;
	}

	/*
	 * 由于其他所有约束类型都会抛出错误，因此这必须是检查约束。首先，处理约束表达式，并在pg_constraint中添加条目。
	 */

	fc_ccbin = fc_domainAddConstraint(fc_domainoid, fc_typTup->typnamespace,
								fc_typTup->typbasetype, fc_typTup->typtypmod,
								fc_constr, NameStr(fc_typTup->typname), fc_constrAddr);

	/*
	 * 如果请求验证该约束，则根据要添加约束的域测试存储在属性中的所有值。
	 */
	if (!fc_constr->skip_validation)
		fc_validateDomainConstraint(fc_domainoid, fc_ccbin);

	/*
	 * 我们必须发送一个sinval消息给该域，以确保任何依赖计划都被重建。由于此命令不会更改域的pg_type行，因此这不会自动发生；手动执行。
	 */
	CacheInvalidateHeapTuple(fc_typrel, fc_tup, NULL);

	ObjectAddressSet(fc_address, TypeRelationId, fc_domainoid);

	/* 清理 */
	table_close(fc_typrel, RowExclusiveLock);

	return fc_address;
}

/*
 * AlterDomainValidateConstraint
 *
 * 实现ALTER DOMAIN .. VALIDATE CONSTRAINT语句。
 */
ObjectAddress AlterDomainValidateConstraint(List *fc_names, const char *fc_constrName)
{
	TypeName   *fc_typename;
	Oid			fc_domainoid;
	Relation	fc_typrel;
	Relation	fc_conrel;
	HeapTuple	fc_tup;
	Form_pg_constraint fc_con;
	Form_pg_constraint fc_copy_con;
	char	   *fc_conbin;
	SysScanDesc fc_scan;
	Datum		fc_val;
	bool		fc_isnull;
	HeapTuple	fc_tuple;
	HeapTuple	fc_copyTuple;
	ScanKeyData fc_skey[3];
	ObjectAddress fc_address;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);
	fc_domainoid = typenameTypeId(NULL, fc_typename);

	/* 在类型表中查找域 */
	fc_typrel = table_open(TypeRelationId, AccessShareLock);

	fc_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_domainoid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_domainoid);

	/* 检查它是否是域，并检查用户是否有 ALTER DOMAIN 的权限 */
	checkDomainOwner(fc_tup);

	/*
	 * 查找并检查目标约束
	 */
	fc_conrel = table_open(ConstraintRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_domainoid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_constrName));

	fc_scan = systable_beginscan(fc_conrel, ConstraintRelidTypidNameIndexId, true,
							  NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (!HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("constraint \"%s\" of domain \"%s\" does not exist",
						fc_constrName, TypeNameToString(fc_typename))));

	fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);
	if (fc_con->contype != CONSTRAINT_CHECK)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("constraint \"%s\" of domain \"%s\" is not a check constraint",
						fc_constrName, TypeNameToString(fc_typename))));

	fc_val = SysCacheGetAttr(CONSTROID, fc_tuple,
						  Anum_pg_constraint_conbin,
						  &fc_isnull);
	if (fc_isnull)
		elog(ERROR, "null conbin for constraint %u",
			 fc_con->oid);
	fc_conbin = TextDatumGetCString(fc_val);

	fc_validateDomainConstraint(fc_domainoid, fc_conbin);

	/*
	 * 现在在我们有机会时更新目录。
	 */
	fc_copyTuple = heap_copytuple(fc_tuple);
	fc_copy_con = (Form_pg_constraint) GETSTRUCT(fc_copyTuple);
	fc_copy_con->convalidated = true;
	CatalogTupleUpdate(fc_conrel, &fc_copyTuple->t_self, fc_copyTuple);

	InvokeObjectPostAlterHook(ConstraintRelationId, fc_con->oid, 0);

	ObjectAddressSet(fc_address, TypeRelationId, fc_domainoid);

	heap_freetuple(fc_copyTuple);

	systable_endscan(fc_scan);

	table_close(fc_typrel, AccessShareLock);
	table_close(fc_conrel, RowExclusiveLock);

	ReleaseSysCache(fc_tup);

	return fc_address;
}

static void fc_validateDomainConstraint(Oid fc_domainoid, char *fc_ccbin)
{
	Expr	   *fc_expr = (Expr *) stringToNode(fc_ccbin);
	List	   *fc_rels;
	ListCell   *fc_rt;
	EState	   *fc_estate;
	ExprContext *fc_econtext;
	ExprState  *fc_exprstate;

	/* 需要一个EState来运行ExecEvalExpr */
	fc_estate = CreateExecutorState();
	fc_econtext = GetPerTupleExprContext(fc_estate);

	/* 为expr构建执行状态 */
	fc_exprstate = ExecPrepareExpr(fc_expr, fc_estate);

	/* 获取基于此域的属性的关系列表 */
	/* ShareLock 足以防止并发数据更改 */

	fc_rels = fc_get_rels_with_domain(fc_domainoid, ShareLock);

	foreach(fc_rt, fc_rels)
	{
		RelToCheck *fc_rtc = (RelToCheck *) lfirst(fc_rt);
		Relation	fc_testrel = fc_rtc->rel;
		TupleDesc	fc_tupdesc = RelationGetDescr(fc_testrel);
		TupleTableSlot *fc_slot;
		TableScanDesc fc_scan;
		Snapshot	fc_snapshot;

		/* 扫描此关系中的所有元组 */
		fc_snapshot = RegisterSnapshot(GetLatestSnapshot());
		fc_scan = table_beginscan(fc_testrel, fc_snapshot, 0, NULL);
		fc_slot = table_slot_create(fc_testrel, NULL);
		while (table_scan_getnextslot(fc_scan, ForwardScanDirection, fc_slot))
		{
			int			fc_i;

			/* 测试属于该域的属性 */
			for (fc_i = 0; fc_i < fc_rtc->natts; fc_i++)
			{
				int			fc_attnum = fc_rtc->atts[fc_i];
				Datum		fc_d;
				bool		fc_isNull;
				Datum		fc_conResult;
				Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_attnum - 1);

				fc_d = slot_getattr(fc_slot, fc_attnum, &fc_isNull);

				fc_econtext->domainValue_datum = fc_d;
				fc_econtext->domainValue_isNull = fc_isNull;

				fc_conResult = ExecEvalExprSwitchContext(fc_exprstate,
													  fc_econtext,
													  &fc_isNull);

				if (!fc_isNull && !DatumGetBool(fc_conResult))
				{
					/*
					 * 原则上，这个错误的辅助信息应该是 errdomainconstraint()，但在实践中，errtablecol() 似乎更有用。由于这段代码只在 ALTER DOMAIN 命令中执行，客户端应该已经知道涉及的是哪个域，以及哪个约束。
					 */
					ereport(ERROR,
							(errcode(ERRCODE_CHECK_VIOLATION),
							 errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint",
									NameStr(fc_attr->attname),
									RelationGetRelationName(fc_testrel)),
							 errtablecol(fc_testrel, fc_attnum)));
				}
			}

			ResetExprContext(fc_econtext);
		}
		ExecDropSingleTupleTableSlot(fc_slot);
		table_endscan(fc_scan);
		UnregisterSnapshot(fc_snapshot);

		/* 在提交之前保持关系锁（XXX 对并发不利） */
		table_close(fc_testrel, NoLock);
	}

	FreeExecutorState(fc_estate);
}

/*
 * get_rels_with_domain
 *
 * 获取所有使用该域的关系 / 属性
 *
 * 结果是一个 RelToCheck 结构体的列表，每个结构体对应一个独特的关系，每个结构体包含一个或多个属于该域类型的属性编号。我们已打开每个关系并获得了指定的锁类型。
 *
 * 我们通过包含派生域类型的属性来支持嵌套域。当前调用者不需要区分正好是给定域的属性和属于派生域的属性。
 *
 * XXX 这是完全破坏的，因为没有办法锁定域以防止在我们的命令运行时添加或删除列。我们可以通过锁定关系来部分保护列删除，但仍然存在竞争条件（查看 pg_depend 条目与获取其引用的关系锁之间的窗口）。此外，同时对所有这些关系保持锁定会造成非平凡的死锁风险。我们可以通过使用最低限度适合的锁（对于大多数调用者为 ShareLock）来降低但不能消除死锁风险。
 *
 * XXX 这个 API 不够充分，无法支持检查位于容器类型（例如复合类型、数组或范围）内的域值。当前如果任何地方存储了包含目标域的容器类型，我们只会报错。
 *
 * 通常用于在向域添加新约束时检索测试列表。
 */
static List * fc_get_rels_with_domain(Oid fc_domainOid, LOCKMODE fc_lockmode)
{
	List	   *fc_result = NIL;
	char	   *fc_domainTypeName = format_type_be(fc_domainOid);
	Relation	fc_depRel;
	ScanKeyData fc_key[2];
	SysScanDesc fc_depScan;
	HeapTuple	fc_depTup;

	Assert(fc_lockmode != NoLock);

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	/*
	 * 我们扫描 pg_depend 来查找依赖于该域的事物。（我们假设可以忽略域的 refobjsubid。）
	 */
	fc_depRel = table_open(DependRelationId, AccessShareLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_refclassid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(TypeRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_refobjid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_domainOid));

	fc_depScan = systable_beginscan(fc_depRel, DependReferenceIndexId, true,
								 NULL, 2, fc_key);

	while (HeapTupleIsValid(fc_depTup = systable_getnext(fc_depScan)))
	{
		Form_pg_depend fc_pg_depend = (Form_pg_depend) GETSTRUCT(fc_depTup);
		RelToCheck *fc_rtc = NULL;
		ListCell   *fc_rellist;
		Form_pg_attribute fc_pg_att;
		int			fc_ptr;

		/* 检查直接依赖的类型 */
		if (fc_pg_depend->classid == TypeRelationId)
		{
			if (get_typtype(fc_pg_depend->objid) == TYPTYPE_DOMAIN)
			{
				/*
				 * 这是一个子域，因此递归地将依赖列添加到输出列表。 
				 * 这有点低效，因为当同一关系的属性具有不同的派生域类型时，我们可能会未能合并 RelToCheck 条目，但改进可能不值得。
				 */
				fc_result = list_concat(fc_result,
									 fc_get_rels_with_domain(fc_pg_depend->objid,
														  fc_lockmode));
			}
			else
			{
				/*
				 * 否则，它是使用该域的某种容器类型，因此如果存在任何此类列则失败。
				 */
				find_composite_type_dependencies(fc_pg_depend->objid,
												 NULL,
												 fc_domainTypeName);
			}
			continue;
		}

		/* 否则，忽略不是关系用户列的依赖项 */
		/* （我们假设系统列从不属于域类型） */
		if (fc_pg_depend->classid != RelationRelationId ||
			fc_pg_depend->objsubid <= 0)
			continue;

		/* 查看我们是否已经有此关系的条目 */
		foreach(fc_rellist, fc_result)
		{
			RelToCheck *fc_rt = (RelToCheck *) lfirst(fc_rellist);

			if (RelationGetRelid(fc_rt->rel) == fc_pg_depend->objid)
			{
				fc_rtc = fc_rt;
				break;
			}
		}

		if (fc_rtc == NULL)
		{
			/* 为此关系找到的第一个属性 */
			Relation	fc_rel;

			/* 在关系上获取请求的锁 */
			fc_rel = relation_open(fc_pg_depend->objid, fc_lockmode);

			/*
			 * 检查行类型是否作为复合类型列存储在任何地方；如果是的话，我们现在必须失败。
			 */
			if (OidIsValid(fc_rel->rd_rel->reltype))
				find_composite_type_dependencies(fc_rel->rd_rel->reltype,
												 NULL,
												 fc_domainTypeName);

			/*
			 * 否则，我们可以忽略关系，除了具有存储和用户选择的列类型的关系。
			 *
			 * XXX 如果索引仅扫描可以满足“col::some_domain”来自
			 * 适当的表达式索引，这也应该检查表达式索引列。
			 */
			if (fc_rel->rd_rel->relkind != RELKIND_RELATION &&
				fc_rel->rd_rel->relkind != RELKIND_MATVIEW)
			{
				relation_close(fc_rel, fc_lockmode);
				continue;
			}

			/* 用足够的空间构建RelToCheck条目以容纳所有属性 */
			fc_rtc = (RelToCheck *) palloc(sizeof(RelToCheck));
			fc_rtc->rel = fc_rel;
			fc_rtc->natts = 0;
			fc_rtc->atts = (int *) palloc(sizeof(int) * RelationGetNumberOfAttributes(fc_rel));
			fc_result = lappend(fc_result, fc_rtc);
		}

		/*
		 * 确认列没有被删除，并且是预期的类型。
		 * 这可以防止在我们获取锁之前发生ALTER DROP COLUMN ... 
		 * 但如果整个表被删除，我们仍然会遇到问题。
		 */
		if (fc_pg_depend->objsubid > RelationGetNumberOfAttributes(fc_rtc->rel))
			continue;
		fc_pg_att = TupleDescAttr(fc_rtc->rel->rd_att, fc_pg_depend->objsubid - 1);
		if (fc_pg_att->attisdropped || fc_pg_att->atttypid != fc_domainOid)
			continue;

		/*
		 * 好吧，将列添加到结果中。我们以列号顺序存储列；这只是一个
		 * 黑客技巧，以提高回归测试输出的可预测性...
		 */
		Assert(fc_rtc->natts < RelationGetNumberOfAttributes(fc_rtc->rel));

		fc_ptr = fc_rtc->natts++;
		while (fc_ptr > 0 && fc_rtc->atts[fc_ptr - 1] > fc_pg_depend->objsubid)
		{
			fc_rtc->atts[fc_ptr] = fc_rtc->atts[fc_ptr - 1];
			fc_ptr--;
		}
		fc_rtc->atts[fc_ptr] = fc_pg_depend->objsubid;
	}

	systable_endscan(fc_depScan);

	relation_close(fc_depRel, AccessShareLock);

	return fc_result;
}

/*
 * checkDomainOwner
 *
 * 检查类型是否实际上是一个域，并且当前用户是否有权限在其上执行ALTER DOMAIN。
 * 如果没有，则抛出错误。
 */
void checkDomainOwner(HeapTuple fc_tup)
{
	Form_pg_type fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 检查这是否实际上是一个域 */
	if (fc_typTup->typtype != TYPTYPE_DOMAIN)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is not a domain",
						format_type_be(fc_typTup->oid))));

	/* 权限检查：必须拥有类型 */
	if (!pg_type_ownercheck(fc_typTup->oid, GetUserId()))
		aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typTup->oid);
}

/*
 * domainAddConstraint - 创建和ALTER DOMAIN之间共享的代码
 */
static char * fc_domainAddConstraint(Oid fc_domainOid, Oid fc_domainNamespace, Oid fc_baseTypeOid,
					int fc_typMod, Constraint *fc_constr,
					const char *fc_domainName, ObjectAddress *fc_constrAddr)
{
	Node	   *fc_expr;
	char	   *fc_ccbin;
	ParseState *fc_pstate;
	CoerceToDomainValue *fc_domVal;
	Oid			fc_ccoid;

	/*
	 * 分配或验证约束名称
	 */
	if (fc_constr->conname)
	{
		if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN,
								 fc_domainOid,
								 fc_constr->conname))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("constraint \"%s\" for domain \"%s\" already exists",
							fc_constr->conname, fc_domainName)));
	}
	else
		fc_constr->conname = ChooseConstraintName(fc_domainName,
											   NULL,
											   "check",
											   fc_domainNamespace,
											   NIL);

	/*
	 * 将raw_expr中的A_EXPR转换为EXPR
	 */
	fc_pstate = make_parsestate(NULL);

	/*
	 * 设置一个CoerceToDomainValue来表示表达式中的VALUE的出现。
	 * 请注意，它将看起来具有基本类型的类型，而不是域的类型。
	 * 这似乎是正确的，因为在检查表达式中，我们不应假设输入值可以被认为是域的成员。
	 */
	fc_domVal = makeNode(CoerceToDomainValue);
	fc_domVal->typeId = fc_baseTypeOid;
	fc_domVal->typeMod = fc_typMod;
	fc_domVal->collation = get_typcollation(fc_baseTypeOid);
	fc_domVal->location = -1;		/* 将在使用时设置 */

	fc_pstate->p_pre_columnref_hook = fc_replace_domain_constraint_value;
	fc_pstate->p_ref_hook_state = (void *) fc_domVal;

	fc_expr = transformExpr(fc_pstate, fc_constr->raw_expr, EXPR_KIND_DOMAIN_CHECK);

	/*
	 * 确保它返回布尔结果。
	 */
	fc_expr = coerce_to_boolean(fc_pstate, fc_expr, "CHECK");

	/*
	 * 修复排序信息。
	 */
	assign_expr_collations(fc_pstate, fc_expr);

	/*
	 * 域不允许变量（这可能是死代码，因为
	 * add_missing_from已经成为历史，但让我们确认一下）。
	 */
	if (list_length(fc_pstate->p_rtable) != 0 ||
		contain_var_clause(fc_expr))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
				 errmsg("cannot use table references in domain check constraint")));

	/*
	 * 转换为字符串形式以进行存储。
	 */
	fc_ccbin = nodeToString(fc_expr);

	/*
	 * 将约束存储在pg_constraint中
	 */
	fc_ccoid =
		CreateConstraintEntry(fc_constr->conname,	/* 约束名称 */
							  fc_domainNamespace,	/* 名称空间 */
							  CONSTRAINT_CHECK, /* 约束类型 */
							  false,	/* 是否可延迟 */
							  false,	/* 是否延迟 */
							  !fc_constr->skip_validation, /* 是否已验证 */
							  InvalidOid,	/* 没有父约束 */
							  InvalidOid,	/* 不是一个关系约束 */
							  NULL,
							  0,
							  0,
							  fc_domainOid,	/* 域约束 */
							  InvalidOid,	/* 没有关联索引 */
							  InvalidOid,	/* 外键字段 */
							  NULL,
							  NULL,
							  NULL,
							  NULL,
							  0,
							  ' ',
							  ' ',
							  NULL,
							  0,
							  ' ',
							  NULL, /* 不是排除约束 */
							  fc_expr, /* 检查约束的树形结构 */
							  fc_ccbin,	/* 检查约束的二进制形式 */
							  true, /* 是本地的 */
							  0,	/* inhcount */
							  false,	/* connoinherit */
							  false);	/* is_internal */
	if (fc_constrAddr)
		ObjectAddressSet(*fc_constrAddr, ConstraintRelationId, fc_ccoid);

	/*
	 * 返回已编译的约束表达式，以便调用例程可以
	 * 执行任何额外的必需测试。
	 */
	return fc_ccbin;
}

/* Parser pre_columnref_hook用于域CHECK约束解析 */
static Node * fc_replace_domain_constraint_value(ParseState *fc_pstate, ColumnRef *fc_cref)
{
	/*
	 * 检查是否有对"value"的引用，如果是，将其替换为
	 * 由domainAddConstraint为我们准备的CoerceToDomainValue。
	 * （我们将VALUE视为名称，而不是关键字，以避免破坏许多
	 * 在过去使用VALUE作为列名的应用程序。）
	 */
	if (list_length(fc_cref->fields) == 1)
	{
		Node	   *fc_field1 = (Node *) linitial(fc_cref->fields);
		char	   *fc_colname;

		Assert(IsA(fc_field1, String));
		fc_colname = strVal(fc_field1);
		if (strcmp(fc_colname, "value") == 0)
		{
			CoerceToDomainValue *fc_domVal = copyObject(fc_pstate->p_ref_hook_state);

			/* 传播位置信息，如果有的话 */
			fc_domVal->location = fc_cref->location;
			return (Node *) fc_domVal;
		}
	}
	return NULL;
}


/*
 * 执行ALTER TYPE RENAME
 */
ObjectAddress RenameType(RenameStmt *fc_stmt)
{
	List	   *fc_names = castNode(List, fc_stmt->object);
	const char *fc_newTypeName = fc_stmt->newname;
	TypeName   *fc_typename;
	Oid			fc_typeOid;
	Relation	fc_rel;
	HeapTuple	fc_tup;
	Form_pg_type fc_typTup;
	ObjectAddress fc_address;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);
	fc_typeOid = typenameTypeId(NULL, fc_typename);

	/* 在类型表中查找类型 */
	fc_rel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(fc_typeOid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_typeOid);
	fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 检查类型的权限 */
	if (!pg_type_ownercheck(fc_typeOid, GetUserId()))
		aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typeOid);

	/* 在非域上使用ALTER DOMAIN？ */
	if (fc_stmt->renameType == OBJECT_DOMAIN && fc_typTup->typtype != TYPTYPE_DOMAIN)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is not a domain",
						format_type_be(fc_typeOid))));

	/*
	 * 如果它是一个复合类型，我们需要检查它确实是一个
	 * 独立的复合类型，而不是表的行类型。我们希望用户
	 * 对于这种情况使用ALTER TABLE而不是ALTER TYPE。
	 */
	if (fc_typTup->typtype == TYPTYPE_COMPOSITE &&
		get_rel_relkind(fc_typTup->typrelid) != RELKIND_COMPOSITE_TYPE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is a table's row type",
						format_type_be(fc_typeOid)),
				 errhint("Use ALTER TABLE instead.")));

	/* 也不允许直接修改数组类型 */
	if (IsTrueArrayType(fc_typTup))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot alter array type %s",
						format_type_be(fc_typeOid)),
				 errhint("You can alter type %s, which will alter the array type as well.",
						 format_type_be(fc_typTup->typelem))));

	/*
	 * 如果类型是复合类型，我们还需要重命名相关的pg_class条目。
	 * RenameRelationInternal将自动调用RenameTypeInternal。
	 */
	if (fc_typTup->typtype == TYPTYPE_COMPOSITE)
		RenameRelationInternal(fc_typTup->typrelid, fc_newTypeName, false, false);
	else
		RenameTypeInternal(fc_typeOid, fc_newTypeName,
						   fc_typTup->typnamespace);

	ObjectAddressSet(fc_address, TypeRelationId, fc_typeOid);
	/* 清理 */
	table_close(fc_rel, RowExclusiveLock);

	return fc_address;
}

/*
 * 更改类型的所有者。
 */
ObjectAddress AlterTypeOwner(List *fc_names, Oid fc_newOwnerId, ObjectType fc_objecttype)
{
	TypeName   *fc_typename;
	Oid			fc_typeOid;
	Relation	fc_rel;
	HeapTuple	fc_tup;
	HeapTuple	fc_newtup;
	Form_pg_type fc_typTup;
	AclResult	fc_aclresult;
	ObjectAddress fc_address;

	fc_rel = table_open(TypeRelationId, RowExclusiveLock);

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);

	/* 在这里使用LookupTypeName，以便可以处理shell类型 */
	fc_tup = LookupTypeName(NULL, fc_typename, NULL, false);
	if (fc_tup == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("type \"%s\" does not exist",
						TypeNameToString(fc_typename))));
	fc_typeOid = typeTypeId(fc_tup);

	/* 复制syscache条目，以便我们可以在下面修改它 */
	fc_newtup = heap_copytuple(fc_tup);
	ReleaseSysCache(fc_tup);
	fc_tup = fc_newtup;
	fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 不允许在类型上执行ALTER DOMAIN */
	if (fc_objecttype == OBJECT_DOMAIN && fc_typTup->typtype != TYPTYPE_DOMAIN)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is not a domain",
						format_type_be(fc_typeOid))));

	/*
	 * 如果它是一个复合类型，我们需要检查它确实是一个
	 * 独立的复合类型，而不是表的行类型。我们希望用户
	 * 对于这种情况使用ALTER TABLE而不是ALTER TYPE。
	 */
	if (fc_typTup->typtype == TYPTYPE_COMPOSITE &&
		get_rel_relkind(fc_typTup->typrelid) != RELKIND_COMPOSITE_TYPE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is a table's row type",
						format_type_be(fc_typeOid)),
				 errhint("Use ALTER TABLE instead.")));

	/* 也不允许直接修改数组类型 */
	if (IsTrueArrayType(fc_typTup))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot alter array type %s",
						format_type_be(fc_typeOid)),
				 errhint("You can alter type %s, which will alter the array type as well.",
						 format_type_be(fc_typTup->typelem))));

	/*
	 * 如果新所有者与现有所有者相同，则视为命令已成功。
	 * 这是为了转储恢复的目的。
	 */
	if (fc_typTup->typowner != fc_newOwnerId)
	{
		/* 超级用户可以始终执行此操作 */
		if (!superuser())
		{
			/* 否则，必须是现有对象的所有者 */
			if (!pg_type_ownercheck(fc_typTup->oid, GetUserId()))
				aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typTup->oid);

			/* 必须能够成为新所有者 */
			check_is_member_of_role(GetUserId(), fc_newOwnerId);

			/* 新所有者必须对命名空间具有创建权限 */
			fc_aclresult = pg_namespace_aclcheck(fc_typTup->typnamespace,
											  fc_newOwnerId,
											  ACL_CREATE);
			if (fc_aclresult != ACLCHECK_OK)
				aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
							   get_namespace_name(fc_typTup->typnamespace));
		}

		AlterTypeOwner_oid(fc_typeOid, fc_newOwnerId, true);
	}

	ObjectAddressSet(fc_address, TypeRelationId, fc_typeOid);

	/* 清理 */
	table_close(fc_rel, RowExclusiveLock);

	return fc_address;
}

/*
 * AlterTypeOwner_oid - 无条件更改类型所有者
 *
 * 此函数会递归处理pg_class条目（如果需要）。它
 * 调用任何必要的访问对象钩子。如果hasDependEntry为真，则该
 * 函数适当地修改pg_shdepend条目（这应当仅在表行类型和数组
 * 类型上传递为false）。
 *
 * 这被ALTER TABLE/TYPE OWNER命令以及REASSIGN
 * OWNED BY使用。它假设调用者已完成所有所需检查。
 */
void AlterTypeOwner_oid(Oid fc_typeOid, Oid fc_newOwnerId, bool fc_hasDependEntry)
{
	Relation	fc_rel;
	HeapTuple	fc_tup;
	Form_pg_type fc_typTup;

	fc_rel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typeOid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_typeOid);
	fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	/*
	 * 如果它是一个复合类型，调用ATExecChangeOwner以便我们正确地修复
	 * pg_class条目。那将回调到
	 * AlterTypeOwnerInternal以处理pg_type条目。
	 */
	if (fc_typTup->typtype == TYPTYPE_COMPOSITE)
		ATExecChangeOwner(fc_typTup->typrelid, fc_newOwnerId, true, AccessExclusiveLock);
	else
		AlterTypeOwnerInternal(fc_typeOid, fc_newOwnerId);

	/* 更新所有者依赖关系引用 */
	if (fc_hasDependEntry)
		changeDependencyOnOwner(TypeRelationId, fc_typeOid, fc_newOwnerId);

	InvokeObjectPostAlterHook(TypeRelationId, fc_typeOid, 0);

	ReleaseSysCache(fc_tup);
	table_close(fc_rel, RowExclusiveLock);
}

/*
 * AlterTypeOwnerInternal - 基本的类型所有者更改。
 *
 * 这个例程简单地修改pg_type条目的所有者，并递归
 * 处理可能的数组类型。
 */
void AlterTypeOwnerInternal(Oid fc_typeOid, Oid fc_newOwnerId)
{
	Relation	fc_rel;
	HeapTuple	fc_tup;
	Form_pg_type fc_typTup;
	Datum		fc_repl_val[Natts_pg_type];
	bool		fc_repl_null[Natts_pg_type];
	bool		fc_repl_repl[Natts_pg_type];
	Acl		   *fc_newAcl;
	Datum		fc_aclDatum;
	bool		fc_isNull;

	fc_rel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(fc_typeOid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_typeOid);
	fc_typTup = (Form_pg_type) GETSTRUCT(fc_tup);

	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));

	fc_repl_repl[Anum_pg_type_typowner - 1] = true;
	fc_repl_val[Anum_pg_type_typowner - 1] = ObjectIdGetDatum(fc_newOwnerId);

	fc_aclDatum = heap_getattr(fc_tup,
							Anum_pg_type_typacl,
							RelationGetDescr(fc_rel),
							&fc_isNull);
	/* 空ACL不需要更改 */
	if (!fc_isNull)
	{
		fc_newAcl = aclnewowner(DatumGetAclP(fc_aclDatum),
							 fc_typTup->typowner, fc_newOwnerId);
		fc_repl_repl[Anum_pg_type_typacl - 1] = true;
		fc_repl_val[Anum_pg_type_typacl - 1] = PointerGetDatum(fc_newAcl);
	}

	fc_tup = heap_modify_tuple(fc_tup, RelationGetDescr(fc_rel), fc_repl_val, fc_repl_null,
							fc_repl_repl);

	CatalogTupleUpdate(fc_rel, &fc_tup->t_self, fc_tup);

	/* 如果它有数组类型，也更新它 */
	if (OidIsValid(fc_typTup->typarray))
		AlterTypeOwnerInternal(fc_typTup->typarray, fc_newOwnerId);

	/* 清理 */
	table_close(fc_rel, RowExclusiveLock);
}

/*
 * 执行ALTER TYPE SET SCHEMA
 */
ObjectAddress AlterTypeNamespace(List *fc_names, const char *fc_newschema, ObjectType fc_objecttype,
				   Oid *fc_oldschema)
{
	TypeName   *fc_typename;
	Oid			fc_typeOid;
	Oid			fc_nspOid;
	Oid			fc_oldNspOid;
	ObjectAddresses *fc_objsMoved;
	ObjectAddress fc_myself;

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_names);
	fc_typeOid = typenameTypeId(NULL, fc_typename);

	/* 不允许在类型上执行ALTER DOMAIN */
	if (fc_objecttype == OBJECT_DOMAIN && get_typtype(fc_typeOid) != TYPTYPE_DOMAIN)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is not a domain",
						format_type_be(fc_typeOid))));

	/* 获取模式OID并检查其权限 */
	fc_nspOid = LookupCreationNamespace(fc_newschema);

	fc_objsMoved = new_object_addresses();
	fc_oldNspOid = AlterTypeNamespace_oid(fc_typeOid, fc_nspOid, fc_objsMoved);
	free_object_addresses(fc_objsMoved);

	if (fc_oldschema)
		*fc_oldschema = fc_oldNspOid;

	ObjectAddressSet(fc_myself, TypeRelationId, fc_typeOid);

	return fc_myself;
}

Oid AlterTypeNamespace_oid(Oid fc_typeOid, Oid fc_nspOid, ObjectAddresses *fc_objsMoved)
{
	Oid			fc_elemOid;

	/* 检查类型的权限 */
	if (!pg_type_ownercheck(fc_typeOid, GetUserId()))
		aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typeOid);

	/* 不允许直接更改数组类型 */
	fc_elemOid = get_element_type(fc_typeOid);
	if (OidIsValid(fc_elemOid) && get_array_type(fc_elemOid) == fc_typeOid)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot alter array type %s",
						format_type_be(fc_typeOid)),
				 errhint("You can alter type %s, which will alter the array type as well.",
						 format_type_be(fc_elemOid))));

	/* 并执行工作 */
	return AlterTypeNamespaceInternal(fc_typeOid, fc_nspOid, false, true, fc_objsMoved);
}

/*
 * 将指定类型移动到新命名空间。
 *
 * 调用者必须已经检查了权限。
 *
 * 此函数会自动递归处理类型的数组类型（如果有的话）。
 * isImplicitArray 仅在进行此内部递归时为 true（外部调用者决不能尝试
 * 直接移动数组类型）。
 *
 * 如果 errorOnTableType 为 true，则如果类型是表类型，函数会报错。
 * 必须使用 ALTER TABLE 将表移动到新的命名空间。
 *
 * 返回类型的旧命名空间 OID。
 */
Oid AlterTypeNamespaceInternal(Oid fc_typeOid, Oid fc_nspOid,
						   bool fc_isImplicitArray,
						   bool fc_errorOnTableType,
						   ObjectAddresses *fc_objsMoved)
{
	Relation	fc_rel;
	HeapTuple	fc_tup;
	Form_pg_type fc_typform;
	Oid			fc_oldNspOid;
	Oid			fc_arrayOid;
	bool		fc_isCompositeType;
	ObjectAddress fc_thisobj;

	/*
	 * 确保我们没有之前移动过该对象。
	 */
	fc_thisobj.classId = TypeRelationId;
	fc_thisobj.objectId = fc_typeOid;
	fc_thisobj.objectSubId = 0;

	if (object_address_present(&fc_thisobj, fc_objsMoved))
		return InvalidOid;

	fc_rel = table_open(TypeRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(fc_typeOid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for type %u", fc_typeOid);
	fc_typform = (Form_pg_type) GETSTRUCT(fc_tup);

	fc_oldNspOid = fc_typform->typnamespace;
	fc_arrayOid = fc_typform->typarray;

	/* 如果类型已经在那里，我们可以跳过接下来的几项检查。 */
	if (fc_oldNspOid != fc_nspOid)
	{
		/* 切换命名空间的常见检查 */
		CheckSetNamespace(fc_oldNspOid, fc_nspOid);

		/* 检查重复的名称（比唯一索引失败更友好） */
		if (SearchSysCacheExists2(TYPENAMENSP,
								  NameGetDatum(&fc_typform->typname),
								  ObjectIdGetDatum(fc_nspOid)))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists in schema \"%s\"",
							NameStr(fc_typform->typname),
							get_namespace_name(fc_nspOid))));
	}

	/* 检测类型是否是复合类型（但不是表行类型） */
	fc_isCompositeType =
		(fc_typform->typtype == TYPTYPE_COMPOSITE &&
		 get_rel_relkind(fc_typform->typrelid) == RELKIND_COMPOSITE_TYPE);

	/* 如果请求，则强制不使用表类型 */
	if (fc_typform->typtype == TYPTYPE_COMPOSITE && !fc_isCompositeType &&
		fc_errorOnTableType)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is a table's row type",
						format_type_be(fc_typeOid)),
				 errhint("Use ALTER TABLE instead.")));

	if (fc_oldNspOid != fc_nspOid)
	{
		/* 好的，修改 pg_type 行 */

		/* tup 是一个副本，所以我们可以直接在上面进行修改 */
		fc_typform->typnamespace = fc_nspOid;

		CatalogTupleUpdate(fc_rel, &fc_tup->t_self, fc_tup);
	}

	/*
	 * 复合类型有 pg_class 条目。
	 *
	 * 我们还需要修改 pg_class 元组以反映模式的变化。
	 */
	if (fc_isCompositeType)
	{
		Relation	fc_classRel;

		fc_classRel = table_open(RelationRelationId, RowExclusiveLock);

		AlterRelationNamespaceInternal(fc_classRel, fc_typform->typrelid,
									   fc_oldNspOid, fc_nspOid,
									   false, fc_objsMoved);

		table_close(fc_classRel, RowExclusiveLock);

		/*
		 * 检查与复合类型相关的约束（我们目前不支持此功能，
		 * 但未来可能会支持）。
		 */
		AlterConstraintNamespaces(fc_typform->typrelid, fc_oldNspOid,
								  fc_nspOid, false, fc_objsMoved);
	}
	else
	{
		/* 如果是域，可能会有约束 */
		if (fc_typform->typtype == TYPTYPE_DOMAIN)
			AlterConstraintNamespaces(fc_typeOid, fc_oldNspOid, fc_nspOid, true,
									  fc_objsMoved);
	}

	/*
	 * 如果有，更新对模式的依赖 --- 表行类型没有一个，
	 * 隐式数组也没有。
	 */
	if (fc_oldNspOid != fc_nspOid &&
		(fc_isCompositeType || fc_typform->typtype != TYPTYPE_COMPOSITE) &&
		!fc_isImplicitArray)
		if (changeDependencyFor(TypeRelationId, fc_typeOid,
								NamespaceRelationId, fc_oldNspOid, fc_nspOid) != 1)
			elog(ERROR, "failed to change schema dependency for type %s",
				 format_type_be(fc_typeOid));

	InvokeObjectPostAlterHook(TypeRelationId, fc_typeOid, 0);

	heap_freetuple(fc_tup);

	table_close(fc_rel, RowExclusiveLock);

	add_exact_object_address(&fc_thisobj, fc_objsMoved);

	/* 递归更改相关的数组类型（如果有） */
	if (OidIsValid(fc_arrayOid))
		AlterTypeNamespaceInternal(fc_arrayOid, fc_nspOid, true, true, fc_objsMoved);

	return fc_oldNspOid;
}

/*
 * AlterType
 *		ALTER TYPE <type> SET (option = ...)
 *
 * 注意：可能允许在这里的更改集合受到许多
 * 不明显的实现限制的约束。在考虑增加新灵活性时请谨慎行事。
 */
ObjectAddress AlterType(AlterTypeStmt *fc_stmt)
{
	ObjectAddress fc_address;
	Relation	fc_catalog;
	TypeName   *fc_typename;
	HeapTuple	fc_tup;
	Oid			fc_typeOid;
	Form_pg_type fc_typForm;
	bool		fc_requireSuper = false;
	AlterTypeRecurseParams fc_atparams;
	ListCell   *fc_pl;

	fc_catalog = table_open(TypeRelationId, RowExclusiveLock);

	/* 创建一个 TypeName 以便我们可以使用标准类型查找机制 */
	fc_typename = makeTypeNameFromNameList(fc_stmt->typeName);
	fc_tup = typenameType(NULL, fc_typename, NULL);

	fc_typeOid = typeTypeId(fc_tup);
	fc_typForm = (Form_pg_type) GETSTRUCT(fc_tup);

	/* 处理选项 */
	memset(&fc_atparams, 0, sizeof(fc_atparams));
	foreach(fc_pl, fc_stmt->options)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_pl);

		if (strcmp(fc_defel->defname, "storage") == 0)
		{
			char	   *fc_a = defGetString(fc_defel);

			if (pg_strcasecmp(fc_a, "plain") == 0)
				fc_atparams.storage = TYPSTORAGE_PLAIN;
			else if (pg_strcasecmp(fc_a, "external") == 0)
				fc_atparams.storage = TYPSTORAGE_EXTERNAL;
			else if (pg_strcasecmp(fc_a, "extended") == 0)
				fc_atparams.storage = TYPSTORAGE_EXTENDED;
			else if (pg_strcasecmp(fc_a, "main") == 0)
				fc_atparams.storage = TYPSTORAGE_MAIN;
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("storage \"%s\" not recognized", fc_a)));

			/*
			 * 验证存储请求。如果类型不是 varlena，它
			 * 当然不支持非 PLAIN 存储。
			 */
			if (fc_atparams.storage != TYPSTORAGE_PLAIN && fc_typForm->typlen != -1)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("fixed-size types must have storage PLAIN")));

			/*
			 * 从 PLAIN 切换到非 PLAIN 是允许的，但这需要
			 * 超级用户，因为我们无法验证该类型的 C 函数
			 * 会支持它。从非 PLAIN 切换到 PLAIN 是
			 * 完全不允许的，因为确保没有表具有该类型的
			 * 大 toasted 值并不实际。在不同的非 PLAIN 设置之间切换是可以的，
			 * 因为这仅构成将来创建的列请求的策略变化。
			 */
			if (fc_atparams.storage != TYPSTORAGE_PLAIN &&
				fc_typForm->typstorage == TYPSTORAGE_PLAIN)
				fc_requireSuper = true;
			else if (fc_atparams.storage == TYPSTORAGE_PLAIN &&
					 fc_typForm->typstorage != TYPSTORAGE_PLAIN)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("cannot change type's storage to PLAIN")));

			fc_atparams.updateStorage = true;
		}
		else if (strcmp(fc_defel->defname, "receive") == 0)
		{
			if (fc_defel->arg != NULL)
				fc_atparams.receiveOid =
					fc_findTypeReceiveFunction(defGetQualifiedName(fc_defel),
											fc_typeOid);
			else
				fc_atparams.receiveOid = InvalidOid;	/* NONE，移除函数 */
			fc_atparams.updateReceive = true;
			/* 替换 I/O 函数需要超级用户。 */
			fc_requireSuper = true;
		}
		else if (strcmp(fc_defel->defname, "send") == 0)
		{
			if (fc_defel->arg != NULL)
				fc_atparams.sendOid =
					fc_findTypeSendFunction(defGetQualifiedName(fc_defel),
										 fc_typeOid);
			else
				fc_atparams.sendOid = InvalidOid;	/* NONE，移除函数 */
			fc_atparams.updateSend = true;
			/* 替换 I/O 函数需要超级用户。 */
			fc_requireSuper = true;
		}
		else if (strcmp(fc_defel->defname, "typmod_in") == 0)
		{
			if (fc_defel->arg != NULL)
				fc_atparams.typmodinOid =
					fc_findTypeTypmodinFunction(defGetQualifiedName(fc_defel));
			else
				fc_atparams.typmodinOid = InvalidOid;	/* NONE，移除函数 */
			fc_atparams.updateTypmodin = true;
			/* 替换 I/O 函数需要超级用户。 */
			fc_requireSuper = true;
		}
		else if (strcmp(fc_defel->defname, "typmod_out") == 0)
		{
			if (fc_defel->arg != NULL)
				fc_atparams.typmodoutOid =
					fc_findTypeTypmodoutFunction(defGetQualifiedName(fc_defel));
			else
				fc_atparams.typmodoutOid = InvalidOid; /* NONE，移除函数 */
			fc_atparams.updateTypmodout = true;
			/* 替换 I/O 函数需要超级用户。 */
			fc_requireSuper = true;
		}
		else if (strcmp(fc_defel->defname, "analyze") == 0)
		{
			if (fc_defel->arg != NULL)
				fc_atparams.analyzeOid =
					fc_findTypeAnalyzeFunction(defGetQualifiedName(fc_defel),
											fc_typeOid);
			else
				fc_atparams.analyzeOid = InvalidOid;	/* NONE，移除函数 */
			fc_atparams.updateAnalyze = true;
			/* 替换分析函数需要超级用户权限。 */
			fc_requireSuper = true;
		}
		else if (strcmp(fc_defel->defname, "subscript") == 0)
		{
			if (fc_defel->arg != NULL)
				fc_atparams.subscriptOid =
					fc_findTypeSubscriptingFunction(defGetQualifiedName(fc_defel),
												 fc_typeOid);
			else
				fc_atparams.subscriptOid = InvalidOid; /* NONE，移除函数 */
			fc_atparams.updateSubscript = true;
			/* 替换下标函数需要超级用户权限。 */
			fc_requireSuper = true;
		}

		/*
		 * CREATE 接受的其余选项不能被更改。
		 * 检查它们，以便我们可以给出有意义的错误消息。
		 */
		else if (strcmp(fc_defel->defname, "input") == 0 ||
				 strcmp(fc_defel->defname, "output") == 0 ||
				 strcmp(fc_defel->defname, "internallength") == 0 ||
				 strcmp(fc_defel->defname, "passedbyvalue") == 0 ||
				 strcmp(fc_defel->defname, "alignment") == 0 ||
				 strcmp(fc_defel->defname, "like") == 0 ||
				 strcmp(fc_defel->defname, "category") == 0 ||
				 strcmp(fc_defel->defname, "preferred") == 0 ||
				 strcmp(fc_defel->defname, "default") == 0 ||
				 strcmp(fc_defel->defname, "element") == 0 ||
				 strcmp(fc_defel->defname, "delimiter") == 0 ||
				 strcmp(fc_defel->defname, "collatable") == 0)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("type attribute \"%s\" cannot be changed",
							fc_defel->defname)));
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("type attribute \"%s\" not recognized",
							fc_defel->defname)));
	}

	/*
	 * 权限检查。如果我们决定命令需要超级用户，则需要超级用户权限，
	 * 否则必须拥有该类型。
	 */
	if (fc_requireSuper)
	{
		if (!superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser to alter a type")));
	}
	else
	{
		if (!pg_type_ownercheck(fc_typeOid, GetUserId()))
			aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typeOid);
	}

	/*
	 * 我们不允许对非简单基本类型执行 ALTER TYPE SET 的所有形式。
	 * 例如，对于复合类型，更改发送/接收函数将是高度不安全的，
	 * 不用说这毫无意义。此外，pg_dump 不支持更改非基本类型的这些属性。
	 * 我们可能会在某个时候放宽这一点，但现在不行。
	 *
	 * 注意：如果您放宽这一点以允许复合类型，请确保
	 * 调整 AlterTypeRecurse 中的 GenerateTypeDependencies 调用。
	 */
	if (fc_typForm->typtype != TYPTYPE_BASE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is not a base type",
						format_type_be(fc_typeOid))));

	/*
	 * 出于相同的原因，不允许直接更改数组类型。
	 */
	if (IsTrueArrayType(fc_typForm))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("%s is not a base type",
						format_type_be(fc_typeOid))));

	/* 好吧，递归更新此类型及其数组/域 */
	fc_AlterTypeRecurse(fc_typeOid, false, fc_tup, fc_catalog, &fc_atparams);

	/* 清理 */
	ReleaseSysCache(fc_tup);

	table_close(fc_catalog, RowExclusiveLock);

	ObjectAddressSet(fc_address, TypeRelationId, fc_typeOid);

	return fc_address;
}

/*
 * AlterTypeRecurse：AlterType() 的一次递归步骤
 *
 * 将 "atparams" 指定的更改应用于由 "typeOid" 标识的类型，
 * 其现有的 pg_type 元组是 "tup"。如果需要，
 * 还递归更新其数组类型。然后搜索该类型上的任何域，
 * 并递归地对这些域应用（大部分）相同的更改。
 *
 * 我们需要这个，因为系统通常假设域从其基本类型继承
 * 许多属性。有关继承内容的详细信息，请参见上述的 DefineDomain()。
 * 数组继承的属性数量较少，但并非没有。
 *
 * 这里存在竞争条件，因为其他事务可能会同时在该基本类型上
 * 添加另一个域；我们会错过更新那个域。因此，要谨慎允许 ALTER TYPE
 * 更改某些属性，否则域与其基本类型不同步将是致命的（例如 typlen）。
 * 在实践中，合理使用 ALTER TYPE 时，似乎竞争不太可能成为
 * 问题。如果确实发生，可以在所有先前事务提交后，
 * 通过重新执行相同的 ALTER TYPE 来修复。
 */
static void fc_AlterTypeRecurse(Oid fc_typeOid, bool fc_isImplicitArray,
				 HeapTuple fc_tup, Relation fc_catalog,
				 AlterTypeRecurseParams *fc_atparams)
{
	Datum		fc_values[Natts_pg_type];
	bool		fc_nulls[Natts_pg_type];
	bool		fc_replaces[Natts_pg_type];
	HeapTuple	fc_newtup;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[1];
	HeapTuple	fc_domainTup;

	/* 由于此函数是递归的，它可能会导致栈溢出 */
	check_stack_depth();

	/* 更新当前类型的元组 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, 0, sizeof(fc_nulls));
	memset(fc_replaces, 0, sizeof(fc_replaces));

	if (fc_atparams->updateStorage)
	{
		fc_replaces[Anum_pg_type_typstorage - 1] = true;
		fc_values[Anum_pg_type_typstorage - 1] = CharGetDatum(fc_atparams->storage);
	}
	if (fc_atparams->updateReceive)
	{
		fc_replaces[Anum_pg_type_typreceive - 1] = true;
		fc_values[Anum_pg_type_typreceive - 1] = ObjectIdGetDatum(fc_atparams->receiveOid);
	}
	if (fc_atparams->updateSend)
	{
		fc_replaces[Anum_pg_type_typsend - 1] = true;
		fc_values[Anum_pg_type_typsend - 1] = ObjectIdGetDatum(fc_atparams->sendOid);
	}
	if (fc_atparams->updateTypmodin)
	{
		fc_replaces[Anum_pg_type_typmodin - 1] = true;
		fc_values[Anum_pg_type_typmodin - 1] = ObjectIdGetDatum(fc_atparams->typmodinOid);
	}
	if (fc_atparams->updateTypmodout)
	{
		fc_replaces[Anum_pg_type_typmodout - 1] = true;
		fc_values[Anum_pg_type_typmodout - 1] = ObjectIdGetDatum(fc_atparams->typmodoutOid);
	}
	if (fc_atparams->updateAnalyze)
	{
		fc_replaces[Anum_pg_type_typanalyze - 1] = true;
		fc_values[Anum_pg_type_typanalyze - 1] = ObjectIdGetDatum(fc_atparams->analyzeOid);
	}
	if (fc_atparams->updateSubscript)
	{
		fc_replaces[Anum_pg_type_typsubscript - 1] = true;
		fc_values[Anum_pg_type_typsubscript - 1] = ObjectIdGetDatum(fc_atparams->subscriptOid);
	}

	fc_newtup = heap_modify_tuple(fc_tup, RelationGetDescr(fc_catalog),
							   fc_values, fc_nulls, fc_replaces);

	CatalogTupleUpdate(fc_catalog, &fc_newtup->t_self, fc_newtup);

	/* 重新构建此类型的依赖关系 */
	GenerateTypeDependencies(fc_newtup,
							 fc_catalog,
							 NULL,	/* 没有默认表达式可用 */
							 NULL,	/* 不要随便使用 typacl */
							 0, /* 我们在上面拒绝了复合类型 */
							 fc_isImplicitArray,	/* 这可能是一个数组 */
							 fc_isImplicitArray,	/* 仅在它是数组时才相关 */
							 false, /* 不要涉及扩展成员资格 */
							 true);

	InvokeObjectPostAlterHook(TypeRelationId, fc_typeOid, 0);

	/*
	 * 数组继承其基本类型的 typmodin 和 typmodout，但没有
	 * 我们在这里关心的其他属性。如果需要，递归到数组
	 * 类型。
	 */
	if (!fc_isImplicitArray &&
		(fc_atparams->updateTypmodin || fc_atparams->updateTypmodout))
	{
		Oid			fc_arrtypoid = ((Form_pg_type) GETSTRUCT(fc_newtup))->typarray;

		if (OidIsValid(fc_arrtypoid))
		{
			HeapTuple	fc_arrtup;
			AlterTypeRecurseParams fc_arrparams;

			fc_arrtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_arrtypoid));
			if (!HeapTupleIsValid(fc_arrtup))
				elog(ERROR, "cache lookup failed for type %u", fc_arrtypoid);

			memset(&fc_arrparams, 0, sizeof(fc_arrparams));
			fc_arrparams.updateTypmodin = fc_atparams->updateTypmodin;
			fc_arrparams.updateTypmodout = fc_atparams->updateTypmodout;
			fc_arrparams.typmodinOid = fc_atparams->typmodinOid;
			fc_arrparams.typmodoutOid = fc_atparams->typmodoutOid;

			fc_AlterTypeRecurse(fc_arrtypoid, true, fc_arrtup, fc_catalog, &fc_arrparams);

			ReleaseSysCache(fc_arrtup);
		}
	}

	/*
	 * 现在我们需要递归到域。然而，一些属性不会被域继承，因此清除这些属性的更新标志。
	 */
	fc_atparams->updateReceive = false;	/* 域使用 F_DOMAIN_RECV */
	fc_atparams->updateTypmodin = false;	/* 域没有 typmods */
	fc_atparams->updateTypmodout = false;
	fc_atparams->updateSubscript = false;	/* 域没有订阅者 */

	/* 如果没有其他操作需要执行，则跳过扫描 */
	if (!(fc_atparams->updateStorage ||
		  fc_atparams->updateSend ||
		  fc_atparams->updateAnalyze))
		return;

	/* 在 pg_type 中搜索可能的域 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_type_typbasetype,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_typeOid));

	fc_scan = systable_beginscan(fc_catalog, InvalidOid, false,
							  NULL, 1, fc_key);

	while ((fc_domainTup = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_type fc_domainForm = (Form_pg_type) GETSTRUCT(fc_domainTup);

		/*
		 * 在非域中不应该有非零的 typbasetype，但我们来检查一下
		 */
		if (fc_domainForm->typtype != TYPTYPE_DOMAIN)
			continue;

		fc_AlterTypeRecurse(fc_domainForm->oid, false, fc_domainTup, fc_catalog, fc_atparams);
	}

	systable_endscan(fc_scan);
}
